Introducción

Este documento presenta un Análisis Exploratorio de Datos (EDA) sobre un conjunto de datos meteorológicos horarios de múltiples estaciones en Argentina. El objetivo es limpiar, procesar y transformar los datos brutos para descubrir patrones, analizar tendencias temporales y geoespaciales, e identificar eventos extremos.

El análisis comienza con una fase de ingesta y limpieza de datos, seguida de la ingeniería de variables (como la desagregación de precipitación y la categorización de vientos), y concluye con una serie de visualizaciones interactivas y un modelado predictivo básico

1. Configuración e Ingesta de Datos

Primero, cargamos todas las librerías necesarias para el análisis, incluyendo tidyverse para la manipulación de datos, leaflet para mapas, y prophet para series temporales.

rm(list = ls())
options(scipen=999)

library(tidyverse)
library(slider)
library("rstudioapi")
library(arrow)
library(zoo)
library(data.table)
library(janitor)
library(leaflet)
library(scales)
library(prophet)
library(htmlwidgets)
library(plotly)
library(viridisLite)
library(png)
if (!requireNamespace("openair", quietly = TRUE)) {
  install.packages("openair", dependencies = TRUE)
}
library(openair)
library(base64enc)
library(tibble)   
library(RColorBrewer)
library(stringi)

setwd(dirname(rstudioapi::getActiveDocumentContext()$path))

No tenemos el dataset completo. Hay que armarlo. Primero empezamos uniendo los archivos de texto, que estan separado por espacios tabulados. Identificamos cada columna con expresiones regulares. Luego, convertimos las columnas de texto a sus tipos de datos correctos (numérico, fecha) y guardamos el resultado en dataset_clima_unificado.Rds para no tener que repetir este costoso proceso.

# -----------------------------------------------------------------
# PASO 2: Definir la ubicación de tus archivos
# -----------------------------------------------------------------
# Usa "." si ya estableciste el directorio de trabajo (recomendado)
ruta_carpeta <- "smn-data/"

# -----------------------------------------------------------------
# PASO 3: La función de procesamiento (REGEX HIPER-REFINADO)
# -----------------------------------------------------------------
procesar_archivo_regex_general <- function(ruta_archivo) {
  
  print(paste("Procesando:", ruta_archivo)) # Progreso
  
  tryCatch({
    
    # --- 1. Leer líneas (latin1) ---
    lineas <- readr::read_lines( ruta_archivo, skip_empty_rows = TRUE, locale = locale(encoding = "latin1") )
    
    # --- 2. Omitir archivos vacíos / solo encabezado ---
    if (is.null(lineas) || length(lineas) <= 2) { return(NULL) }
    lineas_datos_raw <- lineas[-(1:2)]
    if (length(lineas_datos_raw) == 0) { return(NULL) }
    
    # --- 3. Lógica de "Cosido" ---
    lineas_cosidas <- c(); buffer_linea <- ""
    for (linea_actual in lineas_datos_raw) {
      if (nchar(trimws(linea_actual)) == 0) { next }
      if (stringr::str_starts(trimws(linea_actual), "^\\d{8}")) {
        if (nchar(buffer_linea) > 0) { lineas_cosidas <- c(lineas_cosidas, buffer_linea) }
        buffer_linea <- linea_actual
      } else { buffer_linea <- paste0(buffer_linea, linea_actual) }
    }
    if (nchar(buffer_linea) > 0) { lineas_cosidas <- c(lineas_cosidas, buffer_linea) }
    
    # --- 4. Extracción con Expresiones Regulares (HIPER-REFINADO) ---
    # Patrón más específico basado en tipos de datos esperados:
    patron_especifico <- paste0(
      "^",                # Inicio de línea
      "(\\d{8})",         # Grupo 1: FECHA (8 dígitos exactos)
      "\\s+",             # Separador
      "(\\d+|\\s{1,})",   # Grupo 2: HORA (números O espacios)
      "\\s+",             # Separador
      "([-+]?\\d*\\.?\\d+|\\s{1,})", # Grupo 3: TEMP (número con decimal opcional O espacios)
      "\\s+",             # Separador
      "(\\d+|\\s{1,})",   # Grupo 4: HUM (números O espacios)
      "\\s+",             # Separador
      # Grupo 5: PNM (número con decimal opcional O AL MENOS DOS espacios)
      "([-+]?\\d+\\.\\d+|\\s{2,})",
      "\\s+",             # Separador
      "(\\d+|\\s{1,})",   # Grupo 6: DD (SOLO números O espacios)
      "\\s+",             # Separador
      "(\\d+|\\s{1,})",   # Grupo 7: FF (SOLO números O espacios)
      "\\s+",             # Separador
      "(.*?)",            # Grupo 8: NOMBRE (el resto, no goloso)
      "\\s*$"             # Espacios opcionales al final
    )
    
    matches <- str_match(lineas_cosidas, patron_especifico)
    
    # Verificación
    if (is.null(matches) || ncol(matches) != 9 || all(is.na(matches[,2]))) {
      warning(paste("Regex específico falló en:", ruta_archivo, "- Omitiendo."))
      # Podríamos añadir un fallback aquí si fuera necesario, pero confiemos en este patrón
      return(NULL)
    }
    
    # Convertir a data frame
    datos_extraidos <- as.data.frame(matches[, -1, drop = FALSE], stringsAsFactors = FALSE)
    colnames(datos_extraidos) <- c("FECHA", "HORA", "TEMP", "HUM", "PNM", "DD", "FF", "NOMBRE")
    datos_extraidos <- datos_extraidos %>% mutate(across(everything(), trimws))
    
    return(datos_extraidos) # Devolver datos como texto
    
  }, error = function(e) {
    warning(paste("Error GRAVE procesando con Regex:", ruta_archivo, "-", e$message))
    return(NULL)
  })
}
# -----------------------------------------------------------------
# PASO 5: Ejecutar el proceso y combinar todo
# -----------------------------------------------------------------

# 1. Obtener la lista de los archivos .txt
archivos <- list.files(path = ruta_carpeta,
                       pattern = "\\.txt$",
                       full.names = TRUE,
                       ignore.case = TRUE)

print(paste("Se encontraron", length(archivos), "archivos para importar."))
[1] "Se encontraron 2472 archivos para importar."
# 2. Aplicar la función CORRECTA a todos los archivos
#    ¡ASEGÚRATE de que el nombre aquí coincida con el de la función!
dataset_temporal <- purrr::map_dfr(archivos,
                                   procesar_archivo_regex_general,
                                   .id = "archivo_origen")
[1] "Procesando: smn-data/datohorario20180101.txt"
[1] "Procesando: smn-data/datohorario20180102.txt"
[1] "Procesando: smn-data/datohorario20180103.txt"
[1] "Procesando: smn-data/datohorario20180104.txt"
print("Importación (como texto) completada.")
[1] "Importación (como texto) completada."
# -----------------------------------------------------------------
# PASO 6: Convertir columnas al tipo de dato correcto
# -----------------------------------------------------------------
print("Convirtiendo tipos de datos...")
[1] "Convirtiendo tipos de datos..."
dataset_completo <- dataset_temporal %>%
  # Primero, reemplazar cadenas vacías "" o que solo sean espacios con NA
  mutate(across(everything(), ~na_if(trimws(.), ""))) %>%
  mutate(
    # Convertir las columnas numéricas
    across(
      .cols = c(HORA, TEMP, HUM, PNM, DD, FF),
      .fns = as.numeric # Convierte a número, los NA se mantienen
    ),
    
    # Convertir la FECHA
    FECHA = as.Date(FECHA, format = "%d%m%Y")
    
    # NOMBRE ya fue limpiado con trimws dentro de la función y con na_if arriba
  )
Error in `mutate()`:
ℹ In argument: `across(.cols = c(HORA,
  TEMP, HUM, PNM, DD, FF), .fns = as.numeric)`.
Caused by error in `across()`:
! Can't select columns that don't exist.
✖ Column `HORA` doesn't exist.
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.

Primer checkpoint: Para no ejecutar todo lo de arriba

dataset_completo <- readRDS("dataset_clima_unificado.Rds")

2. Limpieza de Datos y Tratamiento de NAs

Un primer resumen y conteo de valores NA revela que, aunque los datos están leídos, no están limpios. Múltiples columnas tienen una alta proporción de valores faltantes o anómalos que debemos tratar.

dataset_met <- dataset_completo

summary(dataset_met)
# proporción de valores NA para estas columnas
attach(dataset_met) # adjuntando para evitar mencionar df$var cada vez

col1 = c() 
col2 = c()
col3 = c()

# bucle 
for (i in (1:dim(dataset_met)[2])){
  col1[i] = colnames(dataset_met)[i]
  col2[i] =  sum(is.na(dataset_met[i]))
  col3[i] = round(col2[i]/dim(dataset_met)[1],4)
}

# creando un dataframe para compilar los valores NA 
(df_NA <- tibble("Columna" = col1, "Valores_NA" = col2, "Proporcion_NA" = col3))

2.1. Limpieza de Temperatura (TEMP)

Comenzamos con la Temperatura. Un histograma nos muestra que la mayoría de los datos se concentran en un rango lógico, pero existen valores atípicos extremos (ej. > 50°C o < -40°C) que son físicamente imposibles.

Filtramos estos valores, convirtiéndolos en NA.

tibble(dataset_met)
summary(dataset_met)

ggplot(dataset_met, aes(x = TEMP)) +
  geom_histogram(binwidth = 0.5) +
  coord_cartesian(xlim = c(-100, 100), ylim = c(0, 1)) +
  labs(
    title = "La temperatura se concentra aproximadamente entre los -40 y 50 grados",
    subtitle = "Todos los demás valores deben ser erróneos"
  )

# Ponemos NA donde la temperatura sea otra que ese intervalo

met_limpio <- dataset_met |>
  mutate(
    TEMP = case_when(
      TEMP > -40 & TEMP < 50 ~ TEMP #Si esta entre esos valores es la misma temperatura, sino pone NA
    )
  )

Esto incrementa nuestros NA en Temperatura, por lo que debemos imputarlos. Nuestra estrategia es iterativa y se basa en promedios móviles: - Primero, intentamos un promedio móvil de 5 días (2 días antes, 2 después) agrupando por estación y hora. - Probamos una segunda imputación con un promedio móvil de 5 horas (2 horas antes, 2 después) agrupando por estación y día. - Esto reduce los NA a uno solo. Inspeccionamos este caso (en OBERA) y vemos que es un registro aislado para ese día, por lo que lo eliminamos.

summary(met_limpio) # Ahora la minima es de -39.9 y la maxima de 45.60

# Pero no queremos que haya NA. ¿Cómo podemos aproximar a la temperatura de ese día? Hacemos el promedio de dos horas atras y dos horas adelante.

met_limpio_1.2 <- met_limpio |>
  arrange(NOMBRE, FECHA) |> #Me doy cuenta que el NOMBRE en las primeras 120 filas son un número (28) o una fecha, que ni siquiera corresponde a la fecha real. Quizas podriamos comparar con el dia anterior a ver que estaciones hay en una y no en la otra pero, al ser tan pocas, prefiero borrarlas.
  slice(-(1:120))

na_temp <- met_limpio_1.2 |>
  filter(is.na(TEMP)) |>
  group_by(FECHA, NOMBRE) |>
  summarise(n = n()) |>
  arrange(desc(n)) 
na_temp
# Hay mas de uno por día por estacion. Es más, hay un dia en una estacion que tiene NA en 22 registros. Cambio de planes: Hacemos de dos dias atras y dos dias adelante en esa misma hora. En los valores que vuelva a haber NA hacemos dos horas atras y dos adelante

# --- Preparación ---

met_iterativo <- met_limpio_1.2

# Obtenemos el conteo inicial de NAs
previous_na_count <- sum(is.na(met_iterativo$TEMP))

cat("Conteo inicial de NAs:", previous_na_count, "\n")

while (TRUE) {
  met_iterativo <- met_iterativo |>
    group_by(NOMBRE, HORA) |>
    arrange(FECHA, .by_group = TRUE) |>
    mutate(
    temp_promedio_movil = slide_dbl(
      TEMP, 
      ~mean(.x, na.rm = TRUE), 
      .before = 2, 
      .after = 2
    ),
    TEMP = if_else(is.na(TEMP), temp_promedio_movil, TEMP)
  ) |>
  ungroup() |>
    select(-temp_promedio_movil) 

  # 4. Calculamos el nuevo conteo de NAs
  new_na_count <- sum(is.na(met_iterativo$TEMP))
  
  cat("Iteración completada. NAs restantes:", new_na_count, "\n")

  # 5. Condición de salida
  if (new_na_count < previous_na_count) {
    previous_na_count <- new_na_count
  } else {
    cat("Estabilizado. No hubo más reducción de NAs. Saliendo del bucle.\n")
    break # ¡Salimos del while!
  }
}

summary(met_iterativo) # 29 NAs restantes

# Probamos con otra lógica. Promedio de una hora antes y una después

previous_na_count <- sum(is.na(met_iterativo$TEMP))

cat("Conteo inicial de NAs:", previous_na_count, "\n")

met_iterativo <- met_iterativo |>
    group_by(NOMBRE, FECHA) |>
    arrange(HORA, .by_group = TRUE) |>
    mutate(
    temp_promedio_movil = slide_dbl(
      TEMP, 
      ~mean(.x, na.rm = TRUE), 
      .before = 2, 
      .after = 2
    ),
    TEMP = if_else(is.na(TEMP), temp_promedio_movil, TEMP)
  ) |>
  ungroup() |>
    select(-temp_promedio_movil) 
  
new_na_count <- sum(is.na(met_iterativo$TEMP))
  
cat("Iteración completada. NAs restantes:", new_na_count, "\n")
# Queda un solo NA, Lo visualizamos

met_iterativo |> 
  arrange(desc(is.na(TEMP)), FECHA)
# Es en OBERA, 2022-09-18

met_iterativo |>
  filter(NOMBRE == "OBERA",
         FECHA == "2022-09-18")
# Solo hay un registro de Obera ese dia. EN las otras columnas tambien tiene NA. Lo eliminamos.

met_limpio_1.4 <- met_iterativo |>
  arrange(desc(is.na(TEMP)), FECHA) |>
  slice(-1)
summary(met_limpio_1.4)

Tras limpiar la temperatura, guardamos el progreso.

saveRDS(met_limpio_1.4, file = "met_limpio_1.4.Rds")

2.2. Limpieza de Humedad (HUM)

Corregimos exitosamente todos los NA de temperatura. Ahora analizamos la humedad.Un histograma muestra valores fuera del rango físico (0-100%). Los filtramos, convirtiéndolos en NA.

ggplot(met_limpio_1.4, aes(x = HUM)) +
  geom_histogram(binwidth = 0.5)
# Como es de esperar, la mayoria de valores estan entre el 0 y el 100. Como no puede haber mas de 100% d ehumedad los otros son NA.

met_limpio_2.1 <- met_limpio_1.4 |>
  mutate(
    HUM = case_when(
      HUM >= 0 & HUM <= 100 ~ HUM #Si esta entre esos valores es la misma temperatura, sino pone NA
    )
  )
summary(met_limpio_2.1)

Estimaremos los valores de humedad que no tenemos con una hora antes y una hora después, ya que es más volátil que la temperatura.

Implementamos un bucle while que aplica un promedio móvil de 3 horas (1 antes, 1 después) agrupado por estación y día, repitiendo hasta que el número de NA se estabilice.

met_limpio_2.2 <- met_limpio_2.1
previous_na_count <- sum(is.na(met_limpio_2.2$HUM))

cat("Conteo inicial de NAs:", previous_na_count, "\n")

while (TRUE) {
  met_limpio_2.2 <- met_limpio_2.2 |>
    group_by(NOMBRE, FECHA) |>
    arrange(HORA, .by_group = TRUE) |>
    mutate(
    hum_promedio_movil = slide_dbl(
      HUM, 
      ~mean(.x, na.rm = TRUE), 
      .before = 1, 
      .after = 1
    ),
    HUM = if_else(is.na(HUM), hum_promedio_movil, HUM)
  ) |>
  ungroup() |>
    select(-hum_promedio_movil) 
  
  new_na_count <- sum(is.na(met_limpio_2.2$HUM))
  
  cat("Iteración completada. NAs restantes:", new_na_count, "\n")

  if (new_na_count < previous_na_count) {
    previous_na_count <- new_na_count
  } else {
    cat("Estabilizado. No hubo más reducción de NAs. Saliendo del bucle.\n")
    break # ¡Salimos del while!
  }
}

summary(met_limpio_2.2)
ggplot(met_limpio_2.1, aes(x = HUM)) +
  geom_histogram(binwidth = 0.5)
ggplot(met_limpio_2.2, aes(x = HUM)) +
  geom_histogram(binwidth = 0.5)

Guardamos el progreso nuevamente.

saveRDS(met_limpio_2.2, file = "met_limpio_2.2.Rds")
met_limpio_2.2 <- readRDS("met_limpio_2.2.Rds")

2.3. Limpieza de Presión (PNM)

Vemos la distribución de Presión, que está medida en hPa.

El histograma de Presión (PNM) es más complejo. Muestra la mayoría de los valores en el rango lógico (900-1050 hPa), pero también cúmulos extraños cerca de 1500 y 3000 hPa.

Al investigar estos valores, descubrimos que se concentran en estaciones específicas (ej. “SALTA AERO”). Una serie temporal de Salta revela que todos sus datos de presión son anómalos, sugiriendo un sensor defectuoso.

Decidimos filtrar todos los valores fuera del rango realista 900-1050 hPa, convirtiéndolos en NA.

met_limpio_3 <- met_limpio_2.2

met_limpio_3 |>
  filter(NOMBRE == "LA QUIACA OBSERVATORIO") |>
  view()
  

ggplot(met_limpio_3, aes(x = PNM)) +
  geom_histogram(binwidth = 0.5)
# Hay una especie de macha alrededor de los 1500hPa y poco más de 3000. Ampliamos
ggplot(met_limpio_3, aes(x = PNM)) +
  geom_histogram(binwidth = 0.5) + 
  coord_cartesian(xlim = c(1375,1625))
ggplot(met_limpio_3, aes(x = PNM)) +
  geom_histogram(binwidth = 0.5) + 
  coord_cartesian(xlim = c(3000,3200))
# ¿Por qué será?

valores_raros <- met_limpio_3 |>
  filter(PNM > 1100 | PNM < 850) |>
  arrange(PNM)

valores_raros_count <- valores_raros |> 
  count(NOMBRE) |> 
  arrange(desc(n))
# Salta es la estacion con mas valores raros

presion_salta_diaria <- met_limpio_3 |>
  filter(NOMBRE == "SALTA AERO") |>
  group_by(FECHA) |>
  summarise(
    PRESION_media = mean(PNM, na.rm = TRUE)
  ) |>
  ungroup()

# Graficar la serie de tiempo
ggplot(presion_salta_diaria, aes(x = FECHA, y = PRESION_media)) +
  geom_line() +
  geom_point() + # Opcional: mostrar los puntos de cada día
  labs(
    title = "Evolución de la Presión Promedio en Bariloche",
    y = "Presión Media Diaria (hPa)",
    x = "Fecha"
  ) +
  theme_minimal()

# Podemos ver que no hay un solo dato en Salta que esté en lo parámetros normales. Muy probablemente el sensor esté roto.

met_limpio_3.1 <- met_limpio_3 |>
  mutate(
    PNM = if_else(
      PNM > 900 & PNM < 1050, PNM, NA_real_)
    )

Codigo borrado, pero en resumen: Se itero tantas veces que hubo sesgo de propagacion. La maxima cantidad de valores que se repetian por estacion era de 561 y pasó a 26876.

Probamos otra estrategia. Usamos el paquete zoo para una imputación más controlada: - na.approx: Interpolación lineal para rellenar huecos pequeños (máx. 2 horas). - na.locf: “Última observación llevada adelante” (LOCF) para huecos de hasta 2 horas. - na.locf(fromLast = TRUE): “Próxima observación traída hacia atrás” (NOCB) para los huecos restantes.

Este método rellena los NA de forma robusta sin distorsionar significativamente la distribución original.

met_relleno_final <- met_limpio_3.1 |>
  group_by(NOMBRE, FECHA) |>
  arrange(HORA, .by_group = TRUE) |>
  mutate(
    PNM_relleno_final = zoo::na.approx(PNM, maxgap = 2, na.rm = FALSE),
    PNM_relleno_final = zoo::na.locf(PNM_relleno_final, na.rm = FALSE, maxgap = 2),
    PNM_relleno_final = zoo::na.locf(PNM_relleno_final, fromLast = TRUE, na.rm = FALSE, maxgap = 2)
  ) |>
  ungroup()

nas_finales <- sum(is.na(met_relleno_final$PNM_relleno_final))
cat("NAs iniciales:", sum(is.na(met_limpio_3.1$PNM)), "\n")
cat("NAs finales restantes:", nas_finales, "\n")

summary(met_relleno_final)
ggplot(met_relleno_final, aes(x = PNM)) +
  geom_histogram(binwidth = 0.5)
ggplot(met_relleno_final, aes(x = PNM_relleno_final)) +
  geom_histogram(binwidth = 0.5) 
# El cambio en la distribucion es apenas perceptible. Me quedo con esta limpieza de NA

No aproximamos más NAs para que no haya sesgo de propagación. Formateamos y guardamos.

met_limpio_3.2 <- met_relleno_final |>
  select(-PNM) |>
  rename(PNM = PNM_relleno_final)
saveRDS(met_limpio_3.2, "met_limpio_3.2.Rds")
met_limpio_3.2 <- readRDS("met_limpio_3.2.Rds")

3. Ingeniería de Variables: Viento (DD y FF)

Ahora analizamos las variables de viento: Dirección (DD) y Velocidad (FF). El histograma de Dirección (DD) muestra picos anómalos en 0 y 990. - Encontramos que DD=0 casi siempre corresponde a FF=0 (Calma). - DD=990 es un valor centinela conocido que significa “Variable”.

Usamos esto para dos propósitos: - Crear una nueva variable categórica DD_categoria (Norte, Noreste, Calmo, Variable). - Limpiar la columna numérica DD: si FF=0, ponemos NA; si DD=990, ponemos NA.

met_limpio_4 <- met_limpio_3.2
summary(met_limpio_4)

ggplot(met_limpio_4, aes(x = DD)) +
  geom_histogram(binwidth = 0.5)

DD_common <- met_limpio_4 |>
  group_by(DD) |>
  summarise(DD = mean(DD), n = n()) |>
  arrange(desc(DD))

# Primero: ¿Qué hacemos con grado = 0? ¿Es lo mismo que 360?

met_limpio_4 |> 
  filter(DD == 0) |>
  count(FF) # Descubrimos que si el grado = 0 en casi todos los casos la velocidad tambien es 0. Decidimos poner NA en los casos donde FF = 0 porque en realidad nunca hubo direccion. Dejarlo en 0 rompera las estadisticas y los graficos. El otro gran pico anomalo esta en DD = 990. Esto es un valor sentinela en metereología. Puede significar NA, calmo o variable.

met_limpio_4.1 <- met_limpio_4 |>
  mutate(
      DD_categoria = case_when(
      FF == 0 ~ "Calmo",
      DD == 990 ~ "Variable",
      is.na(FF) ~ NA_character_,
      (DD > 337.5 | DD <= 22.5) & FF > 0 ~ "Norte",
      DD > 22.5  & DD <= 67.5  ~ "Noreste",
      DD > 67.5  & DD <= 112.5 ~ "Este",
      DD > 112.5 & DD <= 157.5 ~ "Sureste",
      DD > 157.5 & DD <= 202.5 ~ "Sur",
      DD > 202.5 & DD <= 247.5 ~ "Suroeste",
      DD > 247.5 & DD <= 292.5 ~ "Oeste",
      DD > 292.5 & DD <= 337.5 ~ "Noroeste",
      .default = NA_character_
    ),
    DD = case_when(
      FF == 0 ~ NA_real_,
      DD == 0 & is.na(FF) ~ NA_real_,
      DD == 0 & FF > 0 ~ 360,
      DD > 360 ~ NA_real_,
      .default = DD
    ),
    DD_categoria = factor(DD_categoria)
  )
summary(met_limpio_4.1) 

Un histograma de Velocidad del Viento (FF) muestra algunos valores muy altos. Al investigarlos, vemos que provienen de bases antárticas y son, de hecho, valores válidos.


ggplot(met_limpio_4.1) +
  geom_histogram(aes(x = FF))

met_limpio_4.1 |>
  filter(FF > 120) |>
  group_by(NOMBRE) |>
  summarize(n=n()) |>
  arrange(desc(n))
# No hay que filtrar nada. Los vientos fuertes son en bases antarticas

El próximo paso es unir el dataset con las estaciones. Hay algunas estaciones cuyos nombres están mal.

Detectamos que muchas estaciones tienen nombres ligeramente diferentes (ej. “LA QUIACA OBS.” vs “LA QUIACA OBSERVATORIO”). Estandarizamos estos nombres usando case_when para asegurar que las uniones futuras funcionen correctamente.

met_limpio_4.2 <- met_limpio_4.1 |>
  filter(NOMBRE != "}") |>
  mutate(
    NOMBRE = case_when(
      NOMBRE == "BUENOS AIRES" ~ "BUENOS AIRES OBSERVATORIO",
      NOMBRE == "ESC.AVIACION MILITAR AERO" ~ "ESCUELA DE AVIACION MILITAR AERO",
      NOMBRE == "ESCUELA DE AVIACION MILITA                                                                      R AERO" ~ "ESCUELA DE AVIACION MILITAR AERO",
      NOMBRE == "LA QUIACA OBS." ~ "LA QUIACA OBSERVATORIO",
      NOMBRE == "LAS FLORES AERO" ~ "LAS FLORES",
      NOMBRE == "OBERA AERO" ~ "OBERA",
      NOMBRE == "PCIA. ROQUE SAENZ PEÑA AER O   " ~ "PRESIDENCIA ROQUE SAENZ PEÑA AERO",
      NOMBRE == "PILAR OBS." ~ "PILAR OBSERVATORIO",
      NOMBRE == "PCIA. ROQUE SAENZ PEÑA AER                                                                      O" ~ "PRESIDENCIA ROQUE SAENZ PEÑA AERO",
      NOMBRE == "PRESIDENCIA ROQUE SAENZ PE                                                                      ÑA AERO" ~ "PRESIDENCIA ROQUE SAENZ PEÑA AERO",
      NOMBRE == "SAN FERNANDO" ~ "SAN FERNANDO AERO",
      NOMBRE == "VENADO TUERTO" ~ "VENADO TUERTO AERO",
      NOMBRE == "VILLA DE MARIA DEL RIO SEC                                                                      O" ~ "VILLA DE MARIA DEL RIO SECO",
      NOMBRE == "VILLA MARIA DEL RIO SECO" ~ "VILLA DE MARIA DEL RIO SECO",
      .default = NOMBRE 
    )
  )

met_limpio_4.2 |>
  group_by(NOMBRE) |>
  summarise(n = n()) |>
  view()

Volvemos a analizar FF. Vamos a hacer una interpolacion para imputar NAs, con un gap muy corto de dos horas. Primero limpiamos un poco el DF (Volvemos a analizar FF. Vamos a hacer una interpolacion para imputar NAs, con un gap muy corto de dos horas.

Realizamos una pasada final de limpieza: - Normalizamos los nombres de las columnas (ej. FECHA -> fecha) con janitor::clean_names. - Creamos una columna datetime formal. - Eliminamos registros duplicados (misma estación, fecha y hora). - Realizamos una imputación final y conservadora sobre ff (Velocidad del Viento) usando zoo::na.approx y na.locf solo para huecos de 2 horas o menos. columnas, creando datetime y eliminando duplicados) para ya tenerlo listo para los próximos analisis.

met_limpio_4.3 <- met_limpio_4.2 |>
  clean_names() |> #Normalizamos los nombres para el siguiente paso, que va a ser unir los dataframes
  mutate(
    fecha        = as.Date(fecha),
    hora         = as.integer(hora),
    dd_categoria = as.factor(dd_categoria),
    pnm          = as.numeric(pnm),
    # Agregamos columna Datetime
    datetime = lubridate::make_datetime(
      year = lubridate::year(fecha),
      month = lubridate::month(fecha),
      day = lubridate::day(fecha),
      hour = hora,
      tz = "America/Argentina/Buenos_Aires"
    )
  ) |>
  # ====== Duplicados ====================================
  distinct(nombre, fecha, hora, .keep_all = TRUE) %>%

  # ========== Interpolación intra-estación de ff =============
  
  # 1. Ordenar los datos
  arrange(nombre, datetime) |>
  group_by(nombre) |>
  mutate(
    # Paso 1: Interpolación lineal (zoo::na.approx)
    ff_interp = zoo::na.approx(ff,
                               x = as.numeric(datetime),
                               na.rm = FALSE, maxgap = 2),
    
    # Paso 2: Relleno hacia adelante (Last Observation Carried Forward). Se aplica sobre la columna 'ff_interp' recién creada en el paso anterior.
    ff_interp = zoo::na.locf(ff_interp,
                             na.rm = FALSE, maxgap = 2),
    # Paso 3: Relleno hacia atrás (Next Observation Carried Backward). Se aplica sobre el resultado del Paso 2.
    ff_interp = zoo::na.locf(ff_interp,
                             fromLast = TRUE, na.rm = FALSE, maxgap = 2)
  ) |>
  ungroup() |>
  select(-ff) |>
  rename(ff = ff_interp)

met_datos_limpio <- met_limpio_4.3

Este met_datos_limpio es nuestro conjunto de datos horario, limpio y procesado. Lo guardamos.

saveRDS(met_datos_limpio, "met_datos_limpio.Rds")
met_datos_limpio <- readRDS("met_datos_limpio.Rds")

4. Unificación con Metadatos y Datos de Precipitación

Ahora que nuestros datos observacionales están limpios, los enriquecemos. - Cargamos smn_estaciones.csv (que contiene latitud, longitud y provincia) y lo unimos. - Cargamos un dataset separado de smn_precipitaciones-1991-2024.txt. Unimos estos datos.

smn_estaciones <- read_csv("smn_estaciones.csv")
smn_estaciones <- smn_estaciones |>
  clean_names()
met_con_info <- met_datos_limpio |>
  left_join(smn_estaciones, by = "nombre")

procesar_archivo_precipitacion <- function(ruta_archivo) {
  
  print(paste("Procesando (modo precipitación):", ruta_archivo))
  
  tryCatch({
    
    # --- 1. Leer el CSV ---
    datos_precip <- readr::read_csv(
      ruta_archivo,
      locale = locale(encoding = "latin1"),
      show_col_types = FALSE # Oculta los mensajes de tipo de columna
    )
    
    # --- 2. Omitir archivos vacíos ---
    if (is.null(datos_precip) || nrow(datos_precip) == 0) {
      return(NULL)
    }
    
    # --- 3. Limpiar y renombrar columnas ---
    # El nombre de tu tercera columna "Precipitacion (mm) " es problemático
    # (tiene un espacio o caracter raro al final).
    # La forma más robusta de renombrarlo es por su posición.
    
    # Aquí seleccionamos las 3 columnas y les damos nombres limpios
    datos_limpios <- datos_precip |>
      select(
        Estacion = 1,
        Fecha = 2,
        Precipitacion_mm = 3
      )
      
    return(datos_limpios)
    
  }, error = function(e) {
    warning(paste("Error GRAVE procesando (modo precipitación):", ruta_archivo, "-", e$message))
    return(NULL)
  })
}

datos_de_lluvia <- procesar_archivo_precipitacion("smn_precipitaciones-1991-2024/smn_precipitaciones-1991-2024.txt")

datos_de_lluvia <- datos_de_lluvia |>
  rename(
    nro = Estacion,
    fecha = Fecha,
    precipitacion = Precipitacion_mm
  )

df_final <- met_con_info |>
  left_join(
    datos_de_lluvia,
    by = c("nro", "fecha")
  )  
  

df_lluvia_con_info <- datos_de_lluvia |>
  left_join(smn_estaciones, by = "nro")

Guardamos los dataframes resultantes: df_final (datos horarios unidos con metadata y precipitación diaria) y df_lluvia_con_info (datos de precipitación diarios unidos con metadata).

saveRDS(df_final, "df_final.Rds")
saveRDS(df_lluvia_con_info, "df_lluvia_con_info.Rds")


df_final <- readRDS("df_final.Rds")
df_lluvia_con_info <- readRDS("df_lluvia_con_info.Rds")

4.1. Desagregación Temporal de Precipitación

Hay un problema. El df de precipitaciones está por día, pero el de los datos está por hora.

Esto presenta un desafío clave: nuestro dataset principal es horario, pero la precipitación es un total diario. Para analizarlos juntos, debemos desagregar ese total diario en estimaciones horarias.

Implementamos un sistema lógico basado en la humedad: - Si la precipitación diaria es 0, todas las horas son 0. - Plan A: Si hay horas con humedad >= 90%, repartimos la lluvia equitativamente solo entre esas horas. - Plan B: Si no hay horas >= 90%, buscamos horas con humedad >= 70%. Repartimos la lluvia de forma ponderada (más humedad, más lluvia) entre esas horas. - Plan C: Si todas las horas tienen < 70% de humedad, repartimos la lluvia equitativamente entre las 24 horas.

Esto crea la nueva columna precip_horaria.

UMBRAL_ALTO <- 90
UMBRAL_BAJO <- 70

df_final_1.2 <- df_final |>
  group_by(nombre, fecha) |>
  mutate(
    # --- Conversión y Ayudas Plan A (Umbral 90%) ---
    precipitacion = as.numeric(precipitacion),
    es_hora_humeda_alta = (hum >= UMBRAL_ALTO & !is.na(hum)),
    n_horas_humedas_alta = sum(es_hora_humeda_alta),
    
    # --- Ayudas Plan B (Ponderado) ---
    hum_a_ponderar = if_else(hum >= UMBRAL_BAJO & !is.na(hum), hum, 0),
    total_hum_ponderada_dia = sum(hum_a_ponderar, na.rm = TRUE),

    # === El Case When Final ===
    precip_horaria = case_when(
      
      is.na(precipitacion) ~ NA_real_,
      precipitacion == 0 ~ 0,
      
      precipitacion > 0 & n_horas_humedas_alta > 0 & es_hora_humeda_alta == TRUE ~ precipitacion / n_horas_humedas_alta,
      precipitacion > 0 & n_horas_humedas_alta > 0 & es_hora_humeda_alta == FALSE ~ 0,
      
      precipitacion > 0 & n_horas_humedas_alta == 0 & total_hum_ponderada_dia > 0 ~ (hum_a_ponderar / total_hum_ponderada_dia) * precipitacion,
      
      precipitacion > 0 & n_horas_humedas_alta == 0 & total_hum_ponderada_dia == 0 ~ precipitacion / n(),
      
      .default = NA_real_ 
    )
  ) |>
  ungroup() |>
  select(
    -es_hora_humeda_alta, -n_horas_humedas_alta, 
    -hum_a_ponderar, -total_hum_ponderada_dia
  )|>
  arrange(fecha)

# Dice que hay 2189 advertencias con as.numeric(precipitacion)

df_final |>
  filter(is.na(as.numeric(precipitacion))) |>
  distinct(precipitacion) #son /N o NA

Guardamos este dataframe final, que ahora está completo y listo para el análisis.

saveRDS(df_final_1.2, "df_final_1.2.Rds")
Error: object 'df_final_1.2' not found

5. Creación de Datasets Agregados

Ahora creamos dataframes que nos ayudarán más adelante para los gráficos.

Para realizar un análisis a nivel nacional, un simple promedio de todos los registros horarios sería incorrecto (sesgado). Debemos crear un promedio “justo”. - daily_station: Agregamos los datos horarios para obtener resúmenes diarios por estación (media de temp, suma de precip, etc.). - monthly_by_station_fair: Agregamos los resúmenes diarios a mensuales, por estación. Filtramos los meses que no tengan al menos 25 días de datos (para que sean representativos). - monthly_national_fair: Calculamos el promedio nacional “justo”. Tomamos los promedios mensuales de cada estación y calculamos la media de esos valores. Así, cada estación tiene un solo “voto” por mes. Filtramos los meses nacionales que no tengan datos de al menos 60 estaciones.

df <- df_final_1.2 

# ================== AGREGADOS PARA EDA ======================

# ---- Diario por estación ----
daily_station <- df |>
  # Agrupamos por las columnas de la estación y por día/año/mes
  group_by(
    nro, nombre, provincia, latitud, longitud, 
    ymd = fecha,
    year = year(datetime),    
    month = month(datetime)   
  ) %>%
  # Calculamos los resúmenes diarios
  summarise(
    registros  = n(),
    temp_mean  = if (all(is.na(temp))) NA_real_ else mean(temp, na.rm = TRUE),
    temp_sd    = sd(temp, na.rm = TRUE),
    hum_mean   = if (all(is.na(hum))) NA_real_ else mean(hum, na.rm = TRUE),
    pnm_mean   = if (all(is.na(pnm))) NA_real_ else mean(pnm, na.rm = TRUE),
    precip_sum = sum(precip_horaria, na.rm = TRUE),
    .groups = 'drop' 
  )

# ---- Nacional mensual "ingenuo" (para comparar) ----
monthly_national_raw <- df |>
  group_by(
    year = year(datetime),
    month = month(datetime)
  ) |>
  summarise(
    temp_mean = mean(temp, na.rm = TRUE),
    .groups = 'drop'
  ) |>
  mutate(
    ym = as.Date(paste0(year, "-", sprintf("%02d", month), "-01"))
  )

# ---- Nacional mensual "justo" (fair): diario->mensual por estación->promedio nacional ----

monthly_by_station_fair <- daily_station |>
  group_by(
    nro, 
    ym = floor_date(ymd, "month") # redondea la fecha al primer día del mes
  ) |>
  summarise(
    y = mean(temp_mean, na.rm = TRUE),
    ndays = n(),
    .groups = 'drop'
  ) |>
  filter(ndays >= 25)

monthly_national_fair <- monthly_by_station_fair |>
  group_by(ym) |>
  # Calculamos el promedio de los promedios de las estaciones (y) y contamos cuántas estaciones (n_est) pasaron el filtro anterior
  summarise(
    temp_mean = mean(y, na.rm = TRUE),
    n_est = n(),
    .groups = 'drop'
  ) |>
  filter(n_est >= 60) |>
  arrange(ym)


# --- Impresión del resumen de cobertura ---
cat("Cobertura media de estaciones por mes (fair):\n")
monthly_national_fair |>
  summarise(
    mean_n_est = mean(n_est),
    min_n_est = min(n_est),
    max_n_est = max(n_est)
  ) %>%
  print()

Ahora comparamos el método “raw” con el “fair”.

El gráfico resultante muestra que ambas curvas son similares, pero el método “justo” maneja correctamente los datos incompletos al final del período (Oct 2024), donde no promedia porque no cumple los filtros de calidad.

# ---- PASO 1: Combinar los data frames ----
# Usamos bind_rows() para unirlos.
# .id = "metodo" crea una nueva columna llamada 'metodo' que toma el nombre que le dimos a cada dataframe ("Justo" o "Ingenuo").

df_comparativo <- bind_rows(
  Justo   = monthly_national_fair,
  Ingenuo = monthly_national_raw,
  .id = "metodo"
)

ggplot(df_comparativo, aes(x = ym, y = temp_mean, color = metodo)) +
  geom_line(linewidth = 1) +
  geom_point(alpha = 0.5) + 
  labs(
    title = "Comparación de Promedio Mensual de Temperatura Nacional",
    subtitle = "Método 'Justo' (promedio de estaciones) vs. 'Ingenuo' (promedio de lecturas)",
    x = "Fecha",
    y = "Temperatura Media (°C)",
    color = "Método de Cálculo"
  ) +
  theme_minimal() +
  scale_x_date(date_breaks = "2 years", date_labels = "%Y") 

Guardamos estos importantes dataframes agregados.

saveRDS(df, "df.Rds")
saveRDS(daily_station, "daily_station.Rds")
saveRDS(monthly_by_station_fair, "monthly_by_station_fair.Rds")
saveRDS(monthly_national_fair, "monthly_national_fair.Rds")

6. Análisis Visual Exploratorio

Con los datos limpios y agregados, comenzamos la exploración visual.

Bloque 1: Conociendo el Conjunto de Datos

Primero, una visión general de la cobertura de datos.

# =========== INTRO EDUCATIVA (PÚBLICO BÁSICO) ============= 
# ---- 1 fila por estación (para mapa y barras) ----
stations_tbl <- daily_station %>%
  filter(!is.na(latitud) & !is.na(longitud)) %>%
  group_by(nro) %>%
  slice(1) %>%
  ungroup() %>%
  select(nro, nombre, provincia, latitud, longitud)

Un mapa interactivo nos permite explorar la ubicación de las estaciones, coloreadas por provincia.

# ========= BLOQUE 1 – Conociendo el conjunto de datos ==========
provincias_geo <- sf::st_read("shiny_app/data/provincias.geojson")
# Usamos "Set3" o "Paired" que tienen muchos colores distintos.
pal <- colorFactor(
  palette = "Set3", # Una paleta con colores variados (como en tu mapa)
  domain = provincias_geo$nombre # Basamos los colores en los nombres de provincia
)

m_intro <- leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  
  # CAPA 1: Polígonos de Provincias (AHORA NO INTERACTIVOS)
  addPolygons(
    data = provincias_geo,
    fillColor = ~pal(nombre), 
    weight = 1,
    opacity = 1,
    color = "white",
    fillOpacity = 0.7,
    
    # --- CAMBIO CLAVE (Sintaxis correcta) ---
    # Desactivamos el clic y el resaltado
    options = pathOptions(interactive = FALSE) 
  ) %>%
  
  # CAPA 2: Marcadores de Estaciones (ENCIMA Y CLICABLES)
  addCircleMarkers(
    data = stations_tbl,
    lng = ~longitud, 
    lat = ~latitud,
    radius = 4, 
    stroke = TRUE, 
    weight = 1,
    color = "black",
    fillColor = "white",
    fillOpacity = 0.9,
    
    # El popup de la estación ahora funcionará
    popup = ~sprintf("<b>%s</b><br/>Provincia: %s", nombre, provincia)
  ) %>%
  
  setView(lng = -64, lat = -38.4, zoom = 4)

m_intro

Un gráfico de barras cuantifica esto, mostrando el número de estaciones por provincia.

# (2) Barras: cantidad de estaciones por provincia
bars_df <- stations_tbl %>%
  count(provincia, name = "estaciones") %>%
  arrange(desc(estaciones))

# --- Gráfico ggplot modificado ---
p_bars <- ggplot(bars_df, aes(x = reorder(provincia, estaciones), 
                             y = estaciones, 
                             fill = estaciones,
                             # --- AÑADE ESTA LÍNEA ---
                             text = paste("Estaciones:", estaciones) 
                             )) +
  geom_col() +
  scale_fill_distiller(palette = "Blues", direction = 1) + 
  coord_flip() +
  labs(x = NULL, y = "Cantidad de estaciones", title = "Estaciones por provincia") +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none")

# Convertir a plotly
p_bars <- ggplotly(p_bars, tooltip = "text")
p_bars

Finalmente, una serie temporal muestra el número de estaciones activas (que reportan datos) por año. Se observa una notable caída en los años más recientes del conjunto de datos.

# (3) Timeline: estaciones activas por año (simple)
# (Tu código de preparación de datos)
years_active <- daily_station %>%
  group_by(year) %>%
  summarise(estaciones_activas = n_distinct(nombre), .groups = "drop") %>%
  arrange(year)

# --- Cálculo dinámico de los límites del zoom ---
min_val <- min(years_active$estaciones_activas, na.rm = TRUE)
max_val <- max(years_active$estaciones_activas, na.rm = TRUE)
padding <- (max_val - min_val) * 0.2

p_timeline_zoom <- ggplot(years_active, aes(year, estaciones_activas)) +
  geom_area(fill = "#d62728", alpha = 0.3) + 
  geom_line(color = "#d62728", linewidth = 1.2) +
  
  scale_x_continuous(breaks = pretty_breaks()) +
  
  labs(
    x = "Año", y = "Estaciones activas", 
    title = "Caída Reciente en el Conteo de Estaciones Activas",
    subtitle = "Nota: El eje Y (vertical) no inicia en 0 para enfatizar la variación."
  ) +
  
  # --- LA PARTE CLAVE ---
  # Hacemos zoom en el eje Y. Los límites se calculan dinámicamente.
  coord_cartesian(ylim = c(min_val - padding, max_val + padding)) +
  
  theme_minimal(base_size = 13)

p_timeline_zoom <- ggplotly(p_timeline_zoom, tooltip = c("x", "y"))
p_timeline_zoom

Bloque 2: ¿Qué variables se registraron?

Una tabla simple de KPIs (Indicadores Clave de Rendimiento) nos da los promedios generales del dataset.

# ======== BLOQUE 2 – ¿Qué variables se registraron? =========

# (4) KPIs sencillos (tabla) — se imprime en el Rmd
kpis <- daily_station %>%
  summarise(
    # Envuelve los nombres con espacios y caracteres especiales en `backticks`
    `Temp_media (°C)`          = mean(temp_mean, na.rm = TRUE),
    `Humedad_media (%)`        = mean(hum_mean, na.rm = TRUE),
    `Presión_media (hPa)`      = mean(pnm_mean, na.rm = TRUE),
    `Lluvia_diaria_media (mm)` = mean(precip_sum, na.rm = TRUE)
  )

kpis_table <- knitr::kable(kpis, digits = 1, align = "r")
kpis_table

Luego, generamos histogramas para visualizar la distribución de las principales variables diarias: Temperatura, Humedad y Presión.

# (5) Histogramas simples por variable
mk_hist <- function(df, var, titulo, xlab, 
                    bins = 40, 
                    fill_color = "#1f77b4", # <-- 1. Nuevo argumento con un color azul
                    alpha = 0.8) {          # <-- 2. Nuevo argumento para transparencia
  
  gg <- ggplot(df, aes(x = .data[[var]])) +
    geom_histogram(
      bins = bins, 
      boundary = 0, 
      color = "white",      # Mantiene el borde blanco
      fill = fill_color,    # <-- 3. Usa el color de relleno
      alpha = alpha         # <-- 4. Aplica la transparencia
    ) +
    labs(title = titulo, x = xlab, y = "Frecuencia") +
    theme_minimal(base_size = 13) +
    theme(legend.position = "none") # Oculta la leyenda (no necesaria)
  
  ggplotly(gg, tooltip = c("x","y"))
}
dist_temp <- mk_hist(daily_station, "temp_mean",  "¿Cómo se reparte la temperatura?", "Temperatura (°C)")
dist_temp

dist_hum <- mk_hist(daily_station, "hum_mean",   "¿Cómo se reparte la humedad?", "Humedad relativa (%)")
dist_hum

dist_pnm <- mk_hist(daily_station, "pnm_mean",   "¿Cómo se reparte la presión?", "Presión (hPa)")
dist_pnm

Bloque 3: Una mirada rápida a los patrones

¿Cómo interactúan estas variables?

Este gráfico muestra el ciclo estacional promedio de temperatura. Los puntos están coloreados para mostrar claramente los meses fríos (azul) y cálidos (rojo).

# ===================== BLOQUE 3 – Una mirada rápida a los patrones =====================

# (6) Serie mensual promedio nacional (muy simple)
# (1) Preparación de datos (igual que tu código)
monthly_mean <- daily_station %>%
  group_by(month) %>%
  summarise(temp = mean(temp_mean, na.rm = TRUE), .groups = "drop") %>%
  arrange(month) %>%
  mutate(mes = factor(month.abb[month], levels = month.abb))

# (2) Calcular el punto medio de la temperatura para centrar la paleta
mid_temp <- mean(monthly_mean$temp, na.rm = TRUE)

# --- (3) Gráfico ggplot Mejorado ---
p_month <- ggplot(monthly_mean, aes(x = mes, y = temp, group = 1,
                                   text = paste0("Mes: ", mes, "<br>Temp: ", 
                                                 sprintf("%.1f", temp), "°C")
                                   )) +
  
  # --- CAMBIO AQUÍ ---
  # 1. La línea ahora es de un color gris estático.
  geom_line(color = "grey80", linewidth = 1.2) + 
  
  # 2. Los puntos SÍ llevan el color dinámico.
  geom_point(aes(color = temp), size = 3) +
  # --- FIN DEL CAMBIO ---
  
  scale_color_gradient2(
    low = "dodgerblue4",  
    mid = "white",        
    high = "firebrick",     
    midpoint = mid_temp   
  ) +
  
  labs(x = "Mes", y = "Temperatura media (°C)", 
       title = "Ciclo Estacional de Temperatura") +
  theme_minimal(base_size = 13) +
  theme(legend.position = "none")

# --- (4) ggplotly con tooltip personalizado ---
p_month <- ggplotly(p_month, tooltip = "text")
p_month

Usamos un gráfico de hexágonos (geom_hex) para visualizar la densidad de la relación entre temperatura y humedad. Las áreas más oscuras indican las condiciones más frecuentes. La línea de regresión (roja) confirma la correlación negativa: a más calor, menos humedad relativa.

# (7) Relación simple: Temperatura vs Humedad (muestra para no sobrecargar)
set.seed(123)
d_samp <- daily_station %>%
  select(temp_mean, hum_mean) %>%
  tidyr::drop_na()
if (nrow(d_samp) > 3000) d_samp <- d_samp %>% dplyr::slice_sample(n = 3000)

# --- Gráfico con geom_hex ---
p_sc_hex <- ggplot(d_samp, aes(x = temp_mean, y = hum_mean)) +
  
  # 1. Reemplazamos geom_point por geom_hex
  geom_hex(bins = 50) + # 'bins' controla el tamaño de los hexágonos
  
  # 2. Añadimos una escala de color (fill)
  # 'trans = "log10"' es clave: evita que los puntos más densos "saturen" la escala
  scale_fill_gradient(
    low = "lightblue", 
    high = "navyblue", 
    trans = "log10", 
    name = "Conteos" # Título de la leyenda
  ) +
  
  # 3. Mantenemos la línea de regresión (en rojo para que resalte)
  geom_smooth(method = "lm", se = FALSE, color = "red", linewidth = 1) +
  
  labs(x = "Temperatura (°C)", y = "Humedad relativa (%)",
       title = "Densidad de la Relación Temperatura-Humedad") +
  theme_minimal(base_size = 13)

# 4. ggplotly (sin 'tooltip') para que muestre el conteo automáticamente
p_sc_hex <- ggplotly(p_sc_hex)
p_sc_hex

Acá usamos los datos del df original (horarios) para un análisis de densidad más profundo.

Un gráfico de contorno 2D confirma la relación inversa entre temperatura y humedad.

# ==================== CORRELACIONES ==========================
set.seed(123)
df_smpl <- df %>%
  slice_sample(n = 300000)

p_th_contour <- plot_ly(df_smpl, x = ~temp, y = ~hum, 
                        type = "histogram2dcontour",
                        colorscale = "Hot",
                        colorbar = list(title = "<b>Nro. de<br>Observaciones</b>")
                       ) %>%
  layout(
    # --- MODIFICACIÓN 2: Títulos más descriptivos ---
    title = list(text = "Relación entre temperatura y humedad relativa<br><sup>A mayor temperatura, tiende a haber menor humedad. Densidad 2D de 300k obs.</sup>"),
    
    xaxis = list(title = "Temperatura (°C)"),
    yaxis = list(title = "Humedad relativa (%)"),
    
    # --- MODIFICACIÓN 3: Arreglar el margen superior ---
    margin = list(
        b = 80, # Margen inferior para la "Fuente"
        t = 80  # Nuevo margen superior para que entre el título/subtítulo
    ),
    
    annotations = list(
      list(text = "Fuente: SMN – elaboración propia.",
           showarrow = FALSE, xref = "paper", yref = "paper", x = 1, y = -0.15,
           xanchor = 'right', yanchor = 'auto', font = list(size = 10))
    )
  )

p_th_contour

Lo mismo pero entre temperatura y presión.

Este histograma 2D muestra que las temperaturas más altas tienden a coincidir con presiones atmosféricas ligeramente menores, con una gran concentración de datos entre 1008 y 1022 hPa.

# ---- Histograma de Densidad: TEMP vs PNM ----

p_tp <- plot_ly(df_smpl, x = ~temp, y = ~pnm, type = "histogram2d") %>%
  
  # Usar plotly::layout para agrupar TODA la configuración
  plotly::layout(
    
    title = list(text = "Temperaturas más altas tienden a coincidir con presiones atmosféricas ligeramente menores<br><sup>Densidad 2D con concentración en el rango 1008–1022 hPa</sup>"),
    xaxis = list(title = "Temperatura (°C)"),
    yaxis = list(title = "Presión a Nivel del Mar (hPa)"),
    
    # --- MODIFICACIÓN: 'colorbar' ahora vive DENTRO de layout ---
    colorbar = list(title = "<b>Densidad de Horas</b>"), # Le agregué negrita
    
    # El margen que soluciona el corte del título
    margin = list(b = 80, t = 100), 
    
    annotations = list(
      list(text = "Fuente: SMN – elaboración propia.",
           showarrow = FALSE, xref = "paper", yref = "paper", x = 1, y = -0.15,
           xanchor = 'right', yanchor = 'auto', font = list(size = 10))
    )
  )

# Dejar que el notebook renderice el objeto
p_tp

Bloque 4: Análisis Geoespacial (Coropletas)

Creamos un mapa de coropletas (mapa de polígonos coloreados) para visualizar la temperatura media por provincia, revelando el claro gradiente térmico de norte a sur.


# ======= (Opcional) BLOQUE 4 – Mapa simple por provincia ========

station_clima <- daily_station %>%
    group_by(nro, provincia) %>%
    summarise(temp_base = mean(temp_mean, na.rm = TRUE), .groups = "drop")

prov_temp <- station_clima %>%
    group_by(provincia) %>%
    summarise(temp_prov = mean(temp_base, na.rm = TRUE), .groups = "drop") %>%
    mutate(
      provincia_join_key = toupper(iconv(provincia, from = "UTF-8", to = "ASCII//TRANSLIT")),
      provincia_join_key = dplyr::case_when(
        provincia_join_key == "CAPITAL FEDERAL" ~ "CABA",
        provincia_join_key == "TIERRA DEL FUEGO" ~ "TIERRA DEL FUEGO",
        TRUE ~ provincia_join_key
      )
    )
provincias_geo <- provincias_geo %>%
  mutate(
    provincia_join_key = toupper(iconv(nombre, from = "UTF-8", to = "ASCII//TRANSLIT")),
    provincia_join_key = dplyr::case_when(
      provincia_join_key == "CAPITAL FEDERAL" ~ "CABA",
      provincia_join_key == "CIUDAD AUTONOMA DE BUENOS AIRES" ~ "CABA",
      provincia_join_key == "TIERRA DEL FUEGO, ANTARTIDA E ISLAS DEL ATLANTICO SUR" ~ "TIERRA DEL FUEGO",
      TRUE ~ provincia_join_key
    )
  )

map_df <- provincias_geo %>%
    left_join(prov_temp, by = c("provincia_join_key"))

  pal <- colorNumeric("RdYlBu", domain = map_df$temp_prov, reverse = TRUE, na.color = "#BDBDBD")

m_choro <- leaflet(map_df) |>
    addProviderTiles(providers$CartoDB.Positron) |>
    addPolygons(
      fillColor = ~pal(temp_prov), color = "white", weight = 1, opacity = 1,
      fillOpacity = 0.7,
      label = ~htmltools::HTML(sprintf("<b>%s</b><br/>Temp media: %s °C",
                                       nombre, ifelse(is.na(temp_prov), "s/d", sprintf("%.1f", temp_prov))))
    ) |>
    addLegend("bottomright", pal = pal, values = ~temp_prov,
              title = "Temp media (°C)", opacity = 1) |>
    setView(lng = -64, lat = -38.4, zoom = 4)
m_choro


# ===================== FIN INTRO EDUCATIVA (dplyr, sin saves) =====================

A continuación, creamos un mapa interactivo multicapa mucho más avanzado. Este mapa permite al usuario superponer y alternar entre cuatro vistas diferentes: - Estaciones (Temp media): Puntos coloreados por temperatura media. - Estaciones (Amplitud): Puntos coloreados por amplitud térmica (diferencia P90-P10). - Provincias (Temp media): El mapa de coropletas que acabamos de ver. - Provincias (Amplitud): Un mapa de coropletas de la amplitud térmica. Esto proporciona una herramienta muy rica para explorar patrones geoespaciales.

# --- 1. PREPARACIÓN DE DATOS (Tu código original) ----

# (Extremos)
ext <- df %>%
  filter(!is.na(temp)) %>%
  group_by(nro, nombre, provincia, latitud, longitud) %>%
  summarise(
    p10 = quantile(temp, .10, na.rm = TRUE),
    p90 = quantile(temp, .90, na.rm = TRUE),
    amp = p90 - p10, 
    .groups = 'drop' 
  )

# (Resumen de estaciones)
stations <- df %>%
  group_by(nro, nombre, provincia) %>%
  summarise(
    lat = first(latitud),
    lon = first(longitud),
    temp_mean = mean(temp, na.rm = TRUE),
    sd_temp = sd(temp, na.rm = TRUE),
    registros = n(),
    .groups = 'drop'
  ) %>%
  filter(!is.na(lat) & !is.na(lon) & !is.na(temp_mean))

# ---- 2. PRE-CÁLCULO DEL RADIO (Tu código original) ----
stations <- stations %>%
  mutate(
    radius_val = scales::rescale(registros, to = c(3, 12))
  )
ext <- ext %>%
  left_join(
    stations %>% select(nro, registros, radius_val),
    by = "nro"
  )

# --- 3. CARGAR GEOJSON Y AGREGAR DATOS (NUEVO) ---

# 3.1 Funciones de ayuda (CORREGIDA)
normalize_name <- function(name_vector) {
  
  # 1. Normalización (sin acentos, mayúsculas)
  key <- toupper(iconv(name_vector, from = "UTF-8", to = "ASCII//TRANSLIT"))
  
  # 2. Limpieza (aplicar case_when al vector 'key')
  dplyr::case_when(
    key == "CAPITAL FEDERAL" ~ "CABA",
    key == "CIUDAD AUTONOMA DE BUENOS AIRES" ~ "CABA",
    key == "TIERRA DEL FUEGO, ANTARTIDA E ISLAS DEL ATLANTICO SUR" ~ "TIERRA DEL FUEGO",
    # (Mantenemos el valor por defecto 'key')
    TRUE ~ key 
  )
}

# 3.2 Cargar y preparar el GeoJSON (Ahora funcionará)
provincias_sf <- provincias_geo %>%
  mutate(provincia_join_key = normalize_name(nombre))

# 3.3 Agregar datos de ESTACIONES a nivel PROVINCIA
prov_temp <- stations %>%
  mutate(provincia_join_key = normalize_name(provincia)) %>%
  group_by(provincia_join_key) %>%
  summarise(
    temp_prov = mean(temp_mean, na.rm = TRUE),
    registros_prov = sum(registros, na.rm = TRUE)
  )

# 3.4 Agregar datos de AMPLITUD a nivel PROVINCIA
prov_amp <- ext %>%
  mutate(provincia_join_key = normalize_name(provincia)) %>%
  group_by(provincia_join_key) %>%
  summarise(
    amp_prov = mean(amp, na.rm = TRUE)
  )

# 3.5 Unir datos agregados al GeoJSON
map_df_temp <- provincias_sf %>%
  left_join(prov_temp, by = "provincia_join_key")
  
map_df_amp <- provincias_sf %>%
  left_join(prov_amp, by = "provincia_join_key")

# ---- 4. PALETAS Y TÍTULO (Tu código original) ----
pal_mean <- colorNumeric(
  palette = c("#0000FF", "#FFFFFF", "#FF0000"),
  domain = stations$temp_mean, 
  na.color = NA
)
pal_amp  <- colorNumeric(viridisLite::magma(256), domain = ext$amp, na.color = NA)

title_html <- '<div style="background-color: rgba(255,255,255,0.8); 
                      border: 1px solid grey; border-radius: 5px; 
                      padding: 5px 10px; text-align: center;">
              <h4 style="margin:0;">Análisis de Estaciones SMN</h4>
              <span style="font-size: 12px;">Temp. Media vs. Amplitud Térmica</span>
              </div>'

# ---- 5. CREACIÓN DEL MAPA (Modificado) ----
m_map <- leaflet() %>% 
  addTiles() %>%
  addControl(html = title_html, position = "topleft") %>%
  
  # --- Capa 1: Estaciones (Temp media) ---
  addCircleMarkers(
    data = stations,
    lng = ~lon, lat = ~lat, 
    group = "Estaciones (Temp media)", # <-- Grupo 1
    radius = ~radius_val,
    color = ~pal_mean(temp_mean), fillColor = ~pal_mean(temp_mean),
    fillOpacity = 0.85, stroke = FALSE,
    popup = ~paste0("<b>", nombre, "</b><br>", provincia,
                    "<br>Registros: ", registros,
                    "<br>Temp media: ", round(temp_mean,2), " °C",
                    "<br>SD: ", round(sd_temp,1), " °C")
  ) %>%
  addLegend(position="bottomright", pal=pal_mean, values=stations$temp_mean,
            title="Temp media (°C)", group="Estaciones (Temp media)") %>%
  
  # --- Capa 2: Estaciones (Amplitud) ---
  addCircleMarkers(
    data = ext,
    lng = ~longitud, lat = ~latitud, 
    group = "Estaciones (Amplitud)", # <-- Grupo 2
    radius = ~radius_val,
    color = ~pal_amp(amp), fillColor = ~pal_amp(amp),
    fillOpacity = 0.85, stroke = FALSE,
    popup = ~paste0("<b>", nombre, "</b><br>", provincia,
                    "<br>Registros: ", registros,
                    "<br>Amplitud térmica (p90-p10): ", round(amp,1), " °C")
  ) %>%
  addLegend(position="bottomleft", pal=pal_amp, values=ext$amp,
            title="Amplitud (°C)", group="Estaciones (Amplitud)") %>%

  # --- Capa 3: Provincias (Temp media) (NUEVO) ---
  addPolygons(
    data = map_df_temp,
    group = "Provincias (Temp media)", # <-- Grupo 3
    fillColor = ~pal_mean(temp_prov),
    weight = 1, opacity = 1, color = "white", fillOpacity = 0.7,
    highlightOptions = highlightOptions(weight = 3, color = "#666", fillOpacity = 0.9, bringToFront = TRUE),
    label = ~sprintf("<b>%s</b><br/>Temp media prov.: %s °C", 
                     nombre, round(temp_prov, 1))
  ) %>%
  addLegend(position="bottomright", pal=pal_mean, values=stations$temp_mean, # Reusa la paleta
            title="Temp media prov. (°C)", group="Provincias (Temp media)") %>%

  # --- Capa 4: Provincias (Amplitud) (NUEVO) ---
  addPolygons(
    data = map_df_amp,
    group = "Provincias (Amplitud)", # <-- Grupo 4
    fillColor = ~pal_amp(amp_prov),
    weight = 1, opacity = 1, color = "white", fillOpacity = 0.7,
    highlightOptions = highlightOptions(weight = 3, color = "#666", fillOpacity = 0.9, bringToFront = TRUE),
    label = ~sprintf("<b>%s</b><br/>Amplitud prov.: %s °C", 
                     nombre, round(amp_prov, 1))
  ) %>%
  addLegend(position="bottomleft", pal=pal_amp, values=ext$amp, # Reusa la paleta
            title="Amplitud prov. (°C)", group="Provincias (Amplitud)") %>%

  # --- Control de capas (Ahora con 4 grupos) ---
  addLayersControl(
    baseGroups = c("Estaciones (Temp media)", "Provincias (Temp media)", 
                   "Estaciones (Amplitud)", "Provincias (Amplitud)"), 
    options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE)
  )
  
# ---- 6. MOSTRAR EL MAPA ----
m_map

7. Análisis Profundo de Series Temporales

Volvemos a nuestro dataframe “justo” (monthly_national_fair) para un análisis más profundo de las tendencias a lo largo del tiempo.

Un histograma de las temperaturas horarias (usando una muestra) nos permite anotar la media y mediana exactas.

# =========================== VISUALES ===========================

# ---- Histograma de temperatura (muestra) con media/mediana y límites útiles
set.seed(123)
mu       <- mean(df$temp); md <- median(df$temp)
p_hist <- plot_ly(x = sample(df$temp, 200000), type = "histogram", nbinsx = 100) %>%
  plotly::layout(
    title = list(
      text = "La distribución de temperaturas horarias muestra un amplio rango estacional<br><sup>Muestra de hasta 200k registros; líneas punteadas = media y mediana</sup>",
      x = 0.05,
      xanchor = 'left'
    ),
    xaxis = list(title = "Temperatura (°C)"),
    yaxis = list(title = "Frecuencia (conteo de horas)"),
    shapes = list(
      list(type="line", x0=mu, x1=mu, y0=0, y1=1, xref="x", yref="paper", line=list(dash="dash")),
      list(type="line", x0=md, x1=md, y0=0, y1=1, xref="x", yref="paper", line=list(dash="dot"))
    ),
    annotations = list(
      # --- Anotación para la Media (mu = 16.3) ---
      list(
        x = mu, y = 0.88,  # <-- Posición Y
        xref = "x", yref = "paper",
        text = paste0("Media: ", round(mu,1),"°C"),
        xanchor = 'right', # <-- Mueve el texto a la izquierda del punto
        showarrow = TRUE, 
        ax = -40, # <-- Flecha apunta desde la izquierda (-40)
        ay = -40,
        font = list(size = 10)
      ),
      # --- Anotación para la Mediana (md = 16.8) ---
      list(
        x = md, y = 0.82, # <-- Posición Y (más baja)
        xref = "x", yref = "paper",
        text = paste0("Mediana: ", round(md,1),"°C"),
        xanchor = 'left', # <-- Mueve el texto a la derecha del punto
        showarrow = TRUE, 
        ax = 40, # <-- Flecha apunta desde la derecha (40)
        ay = -30,
        font = list(size = 10)
      ),
      # --- Anotación para la fuente (footer) ---
      list(text = "Fuente: Servicio Meteorológico Nacional (SMN) – elaboración propia.",
           showarrow = FALSE, xref = "paper", yref = "paper", x = 1, y = -0.15,
           xanchor = 'right', yanchor = 'auto', font = list(size = 10))
    ),
    margin = list(t = 100, b = 80)
  )

p_hist

Trazamos la serie temporal nacional “justa” y le ajustamos una tendencia lineal. El gráfico muestra un ciclo estacional muy fuerte. Aunque la línea de tendencia parece tener una ligera pendiente negativa, el p-valor (p > 0.05) es alto. Esto indica que la tendencia observada no es estadísticamente significativa y es consistente con el azar. A efectos prácticos, la tendencia en este período es plana.

# 1. Preparar la línea de tendencia (ajustar el modelo lineal)
df_trend <- monthly_national_fair %>% 
  select(ym, temp_mean)

mdl <- lm(temp_mean ~ as.numeric(ym), data = df_trend)

# 2. Calcular los valores predichos
df_trend <- df_trend %>% 
  mutate(predicted = predict(mdl))

# --- INICIO DE LA CORRECCIÓN ---
# 2.1. Extraer los resultados del modelo
model_summary <- summary(mdl)

# 2.2. Obtener la pendiente (slope) y el p-valor (pv)
# El coeficiente [2,1] es la pendiente (estimación de 'as.numeric(ym)')
# El predictor 'as.numeric(ym)' cuenta en días, así que convertimos la pendiente a décadas
slope_daily <- model_summary$coefficients[2, 1]
slope_decade <- slope_daily * 365.25 * 10 # Convertir de días a décadas

# El coeficiente [2,4] es el p-valor de la pendiente
pv <- model_summary$coefficients[2, 4]

p_trend <- plot_ly(df_trend, x = ~ym) %>%
  
  # Capa 1: Datos observados (Serie de tiempo)
  add_trace(y = ~temp_mean, name = 'Temperatura Observada (Media)', 
            type = 'scatter', mode = 'lines+markers', 
            line = list(color = '#1f77b4'), marker = list(size = 4)) %>%
  
  # Capa 2: Línea de Tendencia (Modelo Lineal)
  add_trace(y = ~predicted, name = 'Tendencia Lineal', 
            type = 'scatter', mode = 'lines', 
            line = list(color = '#d62728', dash = 'dash'), 
            showlegend = TRUE) %>%
  
  # --- Layout Definitivo (Títulos Combinados) ---
  layout(
    
    # Combinamos Título y Subtítulo en un solo bloque
    title = list(
      text = paste0(
        # Título (Negrita, Tamaño 18)
        "<b>Fuerte Ciclo Estacional Sin Tendencia Lineal Significativa</b>",
        
        # Salto de línea
        "<br>",
        
        # Subtítulo (Tamaño 12, Color gris)
        "<span style='font-size: 12px; color: gray;'>", 
        "Pendiente calculada: ", 
        sprintf("%.3f °C/década (p-valor: %.2f)", slope_decade, pv),
        ". El p-valor alto (p > 0.05) indica que la tendencia es nula.",
        "</span>"
      ),
      
      # --- Parámetros de Centrado ---
      x = 0.5,            # Posición horizontal (0.5 = centro)
      xanchor = 'center',   # Anclaje (que el centro del texto esté en x=0.5)
      y = 0.95,           # Posición vertical (cerca de la parte superior)
      yanchor = 'top'
    ),
    
    # Ya no usamos 'annotations' para el subtítulo
    
    xaxis = list(title = "Fecha", dtick = "M12", tickformat = "%b %Y"),
    yaxis = list(title = "Temperatura media (°C)"),
    legend = list(title = list(text='<b>Datos</b>')),
    
    # Margen para dar espacio
    margin = list(t = 120) 
  )

p_trend

Este tipo de estudios sobre la climatología y sus anomalías suelen ser con datos mucho más extensos, de 30 años al menos. Acá contamos solo con 5 completos (2018-2023).

Con esa climatología base (2018-2023), calculamos las anomalías mensuales (la diferencia de cada mes con su promedio histórico). El gráfico de barras resultante resalta los períodos cálidos prolongados en 2023 y un notable evento frío en 2024.

# ================== Promedio diario nacional (plotly) ==================

# --------- Parámetros ---------
value_col  <- if ("tmax" %in% names(daily_station)) "tmax" else "temp_mean"
plot_start <- as.Date("2018-01-01")
plot_end   <- as.Date("2023-12-31")
norm_years <- 2018:2023

stopifnot(all(c("ymd","year","month", value_col) %in% names(daily_station)))

# --------- 1) Promedio diario nacional ---------
nat_daily <- daily_station %>%
  filter(ymd >= plot_start, ymd <= plot_end) %>%
  group_by(ymd) %>%
  summarise(value = mean(.data[[value_col]], na.rm = TRUE), .groups = "drop") %>%
  mutate(
    year  = year(ymd),
    doy   = yday(ymd),
    is29f = (month(ymd) == 2 & mday(ymd) == 29)
  ) %>%
  filter(!is29f)

# --------- 2) Climatología diaria (promedio por DOY) ---------
clim_doy <- nat_daily %>%
  filter(year %in% norm_years) %>%
  group_by(doy) %>%
  summarise(clim = mean(value, na.rm = TRUE), .groups = "drop")

# Suavizado tipo SMN (curva negra)
lo <- loess(clim ~ doy, data = clim_doy, span = 0.25, degree = 2, family = "gaussian")
clim_doy <- clim_doy %>%
  mutate(clim_smooth = as.numeric(predict(lo, newdata = data.frame(doy = doy))))

# --------- 3) Unir serie diaria con la climatología ---------
plot_df <- nat_daily %>%
  left_join(clim_doy, by = "doy") %>%
  mutate(
    delta = value - clim_smooth,
    signo = if_else(delta >= 0, "sobre", "bajo")
  )

titulo_var <- if (value_col == "tmax") {
  "Temperatura máxima — Promedio diario nacional"
} else {
  "Temperatura — Promedio diario nacional"
}

y_rng <- range(c(plot_df$value, plot_df$clim_smooth), na.rm = TRUE) + c(-1, 1)

# --------- 4) Gráfico (plotly) ---------
p_anom <- plot_ly(plot_df, x = ~ymd, hoverinfo = "skip") %>%
  # Barras verticales (como linerange en ggplot)
  add_segments(
    y = ~pmin(value, clim_smooth),
    yend = ~pmax(value, clim_smooth),
    xend = ~ymd,
    color = ~signo,
    colors = c("bajo" = "#55C3D3", "sobre" = "#C23BAA"),
    name = "Diferencia",
    showlegend = FALSE
  ) %>%
  # Serie observada
  add_lines(
    y = ~value,
    name = "Promedio diario nacional",
    line = list(color = "rgba(160,160,160,1)", width = 1),
    hovertemplate = "%{x|%d-%m-%Y}<br>Promedio: %{y:.1f}°C<extra></extra>"
  ) %>%
  # “Normal” suavizada
  add_lines(
    y = ~clim_smooth,
    name = sprintf("Normal diaria %d–%d (suavizada)", min(norm_years), max(norm_years)),
    line = list(color = "black", width = 2),
    hovertemplate = "%{x|%d-%m-%Y}<br>Normal: %{y:.1f}°C<extra></extra>"
  ) %>%
  layout(
    title = list(
      text = paste0(
        titulo_var,
        "<br><sup>Argentina — ", format(plot_start, "%Y-%m-%d"),
        " a ", format(plot_end, "%Y-%m-%d"), "</sup>"
      )
    ),
    xaxis = list(title = NULL, dtick = "M3", tickformat = "%b %Y"),
    yaxis = list(title = "Temperatura (°C)", range = y_rng),
    margin = list(b = 80),
    annotations = list(
      list(
        text = sprintf(
          "Normal diaria: promedio %d–%d (loess). Fuente: SMN – elaboración propia.",
          min(norm_years), max(norm_years)
        ),
        showarrow = FALSE, xref = "paper", yref = "paper",
        x = 1, y = -0.18, xanchor = "right", font = list(size = 10)
      )
    )
  )

p_anom
# ================== FIN ======================================================

Comparamos el ciclo diurno (horario) promedio para el verano y el invierno. El gráfico muestra claramente la diferencia en la amplitud térmica diaria y la diferencia de ~12°C entre las estaciones.

diurnal <- df %>%
  filter(!is.na(temp)) %>%
  group_by(hora, m = month(datetime)) %>%
  summarise(temp_mean = mean(temp, na.rm = TRUE), .groups = 'drop') %>%
  mutate(
    season = case_when(
      m %in% c(12, 1, 2) ~ "Verano (DJF)",
      m %in% c(6, 7, 8)  ~ "Invierno (JJA)",
      .default = NA_character_ # Asigna NA al resto (otoño/primavera)
    )
  ) %>%
  filter(!is.na(season)) %>%
  group_by(hora, season) %>%
  summarise(temp_mean = mean(temp_mean, na.rm = TRUE), .groups = 'drop') %>%
  # Convertir 'season' a factor para ordenar en gráficos
  mutate(
    season = factor(season, levels = c("Verano (DJF)", "Invierno (JJA)"))
  )

p_diurnal <- plot_ly(diurnal, x=~hora, y=~temp_mean, color=~season, type='scatter', mode='lines+markers',
                     colors = c("Invierno (JJA)" = "#1f77b4", "Verano (DJF)" = "#d62728")) %>%
  layout(
    title = list(text = "Temperaturas medias diurnas: Verano es ~12°C más cálido que el invierno<br><sup>Promedio horario nacional para Verano (DJF) e Invierno (JJA) del período 2018-2024</sup>"),
    
    xaxis = list(title = "Hora local", nticks = 24),
    yaxis = list(title = "Temperatura media (°C)"),
    legend = list(title = list(text = '<b> Estación </b>')),
    annotations = list(
      list(text = "Fuente: SMN – elaboración propia.",
           showarrow = FALSE, xref = "paper", yref = "paper", x = 1, y = -0.15,
           xanchor = 'right', yanchor = 'auto', font = list(size = 10))
    ),
    margin = list(b = 80)
  )

p_diurnal

8. Análisis Detallados por Variable

8.1. Precipitación

Utilizando el conjunto de datos de precipitación más largo (1991-2024), analizamos la contribución de los eventos de lluvia “fuerte”. Creamos un gráfico de barras apiladas que muestra la precipitación mensual total (promedio nacional) dividida en dos componentes: - Azul: Lluvia proveniente de días “ligeros” (< 50 mm). - Rojo: Lluvia proveniente de días “fuertes” (≥ 50 mm). Esto visualiza el impacto significativo que tienen los eventos extremos en el total de precipitación mensual.

# ============= ANÁLISIS DE PRECIPITACIÓN (1991-2024) ============

# 1. Preparación de Datos: Convertir y añadir bandera de día fuerte
rain_daily_full <- df_lluvia_con_info |>
  mutate(
    FECHA = as.Date(fecha),
    pp = as.numeric(precipitacion)
  ) |>
  filter(!is.na(nro), !is.na(fecha), !is.na(pp)) |>
  select(nro, fecha, pp) |>
  mutate(
    is_heavy_day = pp >= 50
  )

# 2. Componentes Mensuales por Estación: Suma de las componentes
rain_month_station_components <- rain_daily_full |>
  mutate(ym = as.Date(format(fecha, "%Y-%m-01"))) |>
  group_by(nro, ym, is_heavy_day) |>
  summarise(
    pp_component = sum(pp, na.rm = TRUE),
    .groups = 'drop'
  )

# 3. Pivotear y Calcular Promedio Nacional ("Fair")
rain_month_national_stacked_full <- rain_month_station_components |>
  tidyr::pivot_wider(
    names_from = is_heavy_day,
    values_from = pp_component,
    names_prefix = "pp_",
    values_fill = 0
  ) |>
  rename(pp_heavy = pp_TRUE, pp_light = pp_FALSE) |>
  
  # Calcular el Promedio Nacional (Método FAIR)
  group_by(ym) |>
  summarise(
    pp_nat_light = mean(pp_light, na.rm = TRUE),
    pp_nat_heavy = mean(pp_heavy, na.rm = TRUE),
    .groups = 'drop'
  ) |>
  arrange(ym)

# 4. Generación del Gráfico Apilado (Plotly)

p_rain_full <- plot_ly(rain_month_national_stacked_full, x = ~ym) %>%
    add_trace(
    y = ~pp_nat_heavy,
    type = 'bar',
    name = 'En días ≥ 50mm',
    marker = list(color = '#d62728')
  ) %>%
  add_trace(
    y = ~pp_nat_light,
    type = 'bar',
    name = 'En días < 50mm',
    marker = list(color = '#1f77b4')
  ) %>%
  layout(
    barmode = 'stack',
    title = list(text = "Contribución de días de lluvia fuerte (≥ 50mm) a la precipitación mensual<br><sup>Promedio de acumulados mensuales por estación para el periodo 1991–2024</sup>"),
    xaxis = list(
      title = "Fecha",
      dtick = "M24", # Marcar cada 2 años
      tickformat = "%Y"
    ),
    yaxis = list(title = "Precipitación mensual promedio (mm)"),
    
    annotations = list(
      list(text = "Fuente: SMN – elaboración propia (datos de precipitación desde 1991).",
           showarrow = FALSE, xref = "paper", yref = "paper", x = 1, y = -0.15,
           xanchor = 'right', yanchor = 'auto', font = list(size = 10))
    ),
    margin = list(b = 80)
  )

p_rain_full

En promedio, ¿cuántos milímetros recibe mensualmente una ubicación aleatoria en Argentina de la lluvia que proviene de eventos torrenciales? El tamaño de la barra roja refleja la rareza del evento, ya que es la la suma de los mm de lluvia fuerte por mes dividio el numero de estaciones. La suma de ambas barras es el promedio de precipitación mensual.

A continuación, creamos un mapa para visualizar dónde ocurren estos eventos extremos. Los círculos representan estaciones, con el tamaño y color indicando la frecuencia de días con lluvia >= 50 mm. Se observa una clara concentración en las regiones NEA/Litoral.

# Mapa de eventos de precipitación extrema (≥ 50 mm/día)
# Lluvia diaria por estación-fecha + lat/lon calculados en j
rain_daily <- df %>%
  # Agrupar por estación y fecha
  group_by(nro, nombre, provincia, fecha = as.Date(datetime)) %>%
  
  # Calcular precipitación diaria total (pp) y obtener coords
  summarise(
    pp = sum(precip_horaria, na.rm = TRUE),
    lat = first(latitud), # Equivalente a latitud[1L]
    lon = first(longitud),# Equivalente a longitud[1L]
    .groups = 'drop'
  )

# ======================================================
# 2. heavy50_day: Filtrar umbral (>= 50 mm/día) y coords válidas
# ======================================================

heavy50_day <- rain_daily %>%
  filter(pp >= 50 & is.finite(lat) & is.finite(lon))

# ======================================================
# 3. heavy50: Agregar por estación (contar eventos y máximo diario)
# ======================================================

heavy50 <- heavy50_day %>%
  # Agrupar por las características únicas de la estación
  group_by(nro, nombre, provincia) %>%
  
  # Calcular métricas de eventos extremos
  summarise(
    eventos_50mm = n(),           # Equivalente a .N
    max_diario   = max(pp, na.rm = TRUE),
    lat          = first(lat),    # Tomar la primera latitud válida
    lon          = first(lon),    # Tomar la primera longitud válida
    .groups = 'drop'
  )

# ======================================================
# 4. Paleta de color (Método Tidyverse/Leaflet)
# ======================================================

pal_events <- colorNumeric(brewer.pal(9, "Blues"), 
                           domain = heavy50$eventos_50mm)

# Mapa
m_ext <- leaflet(heavy50) %>% addTiles() %>%
  addCircleMarkers(
    lng = ~lon, lat = ~lat,
    radius = ~pmin(14, 4 + scales::rescale(eventos_50mm, to = c(2, 12))),
    color = ~pal_events(eventos_50mm), fillColor = ~pal_events(eventos_50mm),
    fillOpacity = 0.9, stroke = FALSE,
    popup = ~paste0(
      "<b>", nombre, "</b><br>", provincia,
      "<br>Eventos ≥50 mm/día: ", eventos_50mm,
      "<br>Máx diario: ", round(max_diario, 1), " mm"
    )
  ) %>%
  
  # --- MODIFICACIÓN 2: Título de leyenda más conciso ---
  addLegend("bottomright", pal = pal_events, values = ~eventos_50mm,
            title = "Frecuencia de Eventos") 
  
# --- MODIFICACIÓN 3: Título movido a la esquina superior izquierda (topleft) ---
title_ext_html <- htmltools::tags$div(
  style = "width: 280px;",
  htmltools::tags$h4("Eventos de precipitación extrema (≥ 50 mm/día)"),
  htmltools::tags$p("Mayor concentración de eventos en NEA/Litoral. El tamaño del punto es proporcional al conteo.", 
                    style = "font-size: small;"),
  htmltools::tags$em("Fuente: SMN – elaboración propia.", style = "font-size: small;")
)

m_ext <- m_ext %>%
  addControl(title_ext_html, position = "topleft")

m_ext

8.2. Demanda Energética (Grados-Día)

Calculamos los Grados-Día de Calefacción (HDD) y Enfriamiento (CDD). Estas métricas estiman la demanda de energía. - HDD (Azul): Mide cuánto frío hizo, indicando la demanda de calefacción. - CDD (Rojo): Mide cuánto calor hizo, indicando la demanda de refrigeración (aire acondicionado). El gráfico de áreas muestra la clara estacionalidad de la demanda energética: picos de HDD en invierno y picos de CDD en verano.

# --- Definición de Temperaturas Base Ajustadas ---
T_BASE_CALENTAMIENTO <- 14 # Típica base ajustada para calefacción (HDD)
T_BASE_ENFRIAMIENTO <- 24 # Típica base para enfriamiento (CDD)

# ======================================================
# 1. Grados-día DIARIOS por ESTACIÓN (CON BASES AJUSTADAS)
# ======================================================

dd_station_day_mejorado <- df %>%
  filter(!is.na(temp)) %>%
  group_by(nro, fecha = as.Date(datetime)) %>%
  
  # Fórmulas ajustadas con T_BASE_CALENTAMIENTO (16°C) y T_BASE_ENFRIAMIENTO (22°C)
  summarise(
    # HDD: Necesidad de Calefacción (base 16°C)
    HDD = sum(pmax(0, T_BASE_CALENTAMIENTO - temp)) / 24, 
    
    # CDD: Necesidad de Enfriamiento (base 22°C)
    CDD = sum(pmax(0, temp - T_BASE_ENFRIAMIENTO)) / 24, 
    .groups = 'drop'
  )

# ======================================================
# 2. Acumulado Mensual y 3. Mediana Nacional
# ======================================================

dd_month_national_mejorado <- dd_station_day_mejorado %>%
  
  # 2. Acumulado Mensual por Estación
  group_by(
    nro, 
    ym = floor_date(fecha, "month") 
  ) %>%
  summarise(
    HDD_month = sum(HDD, na.rm = TRUE),
    CDD_month = sum(CDD, na.rm = TRUE),
    .groups = 'drop'
  ) %>%
  
  # 3. Mediana Nacional "Justa"
  group_by(ym) %>%
  summarise(
    HDD_nat = median(HDD_month, na.rm = TRUE),
    CDD_nat = median(CDD_month, na.rm = TRUE),
    .groups = 'drop'
  ) %>%
  arrange(ym)

# ---
# 4. Gráfico de ÁREA (La alternativa)
# ---
p_dd <- plot_ly(dd_month_national, x = ~ym) %>%
  
  # Capa de Calefacción (HDD)
  add_trace(
    y = ~HDD_nat, 
    name = "Calefacción (HDD)", 
    type = 'scatter', 
    mode = 'lines',
    line = list(color = '#1f77b4'), # Azul
    fill = 'tozeroy',
    fillcolor = 'rgba(31, 119, 180, 0.4)' # Mismo azul, con transparencia
  ) %>%
  
  # Capa de Refrigeración (CDD)
  add_trace(
    y = ~CDD_nat, 
    name = "Refrigeración (CDD)", 
    type = 'scatter', 
    mode = 'lines',
    line = list(color = '#d62728'), # Rojo/Naranja
    fill = 'tozeroy', # <-- ¡LA MAGIA ESTÁ AQUÍ!
    fillcolor = 'rgba(214, 39, 40, 0.4)' # Mismo rojo, con transparencia
  ) %>%
  
  # --- Layout ---
  layout(
    title = list(text = "La demanda energética potencial por clima sigue un fuerte ciclo estacional<br><sup>Mediana de Grados-Día (HDD/CDD) por estación, umbral 18°C</sup>"),
    xaxis = list(
      title = "Fecha",
      dtick = "M12",
      tickformat = "%b %Y"
    ),
    yaxis = list(title = "Grados-día acumulados (Mediana)"), 
    legend = list(title = list(text='<b> Demanda </b>')),
    annotations = list(
      list(text = "Fuente: SMN – elaboración propia.",
           showarrow = FALSE, xref = "paper", yref = "paper", x = 1, y = -0.15,
           xanchor = 'right', yanchor = 'auto', font = list(size = 10))
    ),
    margin = list(b = 80)
  )
p_dd

8.3. Viento (Rosa de Vientos)

Generamos una “Rosa de Vientos” para la estación con más registros. Este gráfico especializado muestra la frecuencia e intensidad del viento por dirección. En este ejemplo, se observa un claro predominio de vientos del Norte y Sudeste.

# ==================== ROSA DE VIENTOS ========================
# 1. Identificar la estación con más datos
st <- df %>%
  count(nombre, sort = TRUE) %>%
  slice(1) %>%
  pull(nombre) # Extraer el valor como un vector simple

# 2. Filtrar el data frame original para obtener los datos de viento
wind_df <- df %>%
  filter(nombre == st) %>%
  select(
    ws = ff,     # Velocidad del viento (wind speed)
    wd = dd,     # Dirección del viento (wind direction)
    date = datetime # Fecha y hora
  )

cat(sprintf("Rosa de vientos para estación %s:\n", st))

# Definición de los breaks originales
my_breaks <- c(0, 2, 4, 6, 8, 12, 20, 48)

# --- TU NUEVA ESTRATEGIA DE LABELS ---
# Creamos 8 labels (etiquetas), una para CADA punto de break
# (0, 2, 4, 6, 8, 12, 20, y el final 48)
my_key_labels <- c("0", "2", "4", "6", "8", "12", "20", "48")

wind_plot <- openair::windRose(
  wind_df,
  ws = "ws",
  wd = "wd",
  breaks = my_breaks, # Los breaks para el cálculo
  paddle = FALSE,
  key.position = "bottom",
  auto.text = FALSE,
  
  main = paste("Predominio de vientos del Norte y Sudeste en la estación", as.character(st)),
  sub = "La frecuencia y la intensidad (m/s) definen el patrón local.\nFuente: SMN – elaboración propia.",

  # --- AJUSTE CLAVE: Usar el argumento 'key' como una lista ---
  # Esto le pasa opciones directamente a la función 'draw.colorkey' de lattice
  key = list(
    # 'at' le dice dónde poner los 'ticks' de color (los 8 valores de break)
    at = my_breaks, 
    
    # 'labels' le dice qué texto poner en esos 'ticks' (las 8 etiquetas que creamos)
    labels = my_key_labels,
    
    # 'cex' controla el tamaño de la fuente de los labels de la leyenda
    cex = 0.9, # Reducimos un poco la fuente para que quepa bien
    
    # 'space' controla dónde va la leyenda
    space = "bottom"
  ),
  
  # A openair le gusta tener un key.footer para las unidades
  key.footer = "(m/s)",
  
  # Mantener los ajustes de espacio que arreglaron el título/subtítulo
  par.settings = list(
    layout.heights = list(
      bottom.padding = 4, 
      sub = 4             
    ),
    fontsize = list(text = 9, points = 9) # Mantenemos la fuente legible
  )
)

wind_plot

Llevamos esto un paso más allá y creamos un mapa interactivo de Rosas de Viento. - El script genera un PNG miniatura de la rosa de vientos para cada estación. - El mapa agrupa estas rosas en clústeres. Al hacer zoom, los clústeres se expanden (“spiderfy”). - Una capa de “Resumen” muestra círculos simples coloreados por la velocidad media del viento. - Al hacer clic en una rosa miniatura, aparece una versión más grande y detallada en el pop-up.

set.seed(42) # Para reproducibilidad del clustering

# --- 1) Definición de Estilo (El que te gustó) ---
output_dir <- "wind_roses"
if (!dir.exists(output_dir)) {
  dir.create(output_dir)
}

# Breaks y Labels que definimos (para la leyenda 'key = list')
my_breaks <- c(0, 2, 4, 6, 8, 12, 20, 48)
my_key_labels <- c("0", "2", "4", "6", "8", "12", "20", "48")

# --- 2) Información de Estaciones (dplyr) ---
stations_info <- df %>%
  filter(is.finite(latitud) & is.finite(longitud)) %>%
  group_by(nro, nombre, provincia) %>%
  summarise(
    lat = first(latitud),
    lon = first(longitud),
    .groups = 'drop'
  ) %>%
  filter(is.finite(lat) & is.finite(lon))

# --- 3) Función 'make_wind_rose_png' (CORREGIDA) ---

make_wind_rose_png <- function(stn_dt, st_name, file_png, width, height, res,
                               is_thumb = TRUE) {
  
  grDevices::png(filename = file_png, width = width, height = height, res = res, bg = "transparent")
  on.exit(grDevices::dev.off(), add = TRUE)
  
  if (is_thumb) {
    # --- THUMBNAIL (Método robusto) ---
    # Ocultamos todo para el ícono
    openair::windRose(
      stn_dt, ws = "ff", wd = "dd",
      breaks = my_breaks,
      paddle = FALSE, 
      auto.text = FALSE,
      key = FALSE,      # Oculta la leyenda
      main = " ",       # Oculta títulos
      sub = " ",
      xlab = " ",
      ylab = " "
    )
    
  } else {
    # --- FULL PNG (Aplicando la solución que pediste) ---
    # (Este es el bloque de código exacto de tu ejemplo)
    openair::windRose(
      stn_dt,
      ws = "ff", 
      wd = "dd", 
      breaks = my_breaks,
      paddle = FALSE,
      key.position = "bottom",
      auto.text = FALSE,
      
      main = paste("Rosa de Viento:", st_name),
      sub = "Frecuencia e intensidad (m/s). Fuente: SMN",
      
      # --- AJUSTE CLAVE: Usar el argumento 'key' ---
      key = list(
        at = my_breaks, 
        labels = my_key_labels, # Labels limpios ("0", "2"...)
        cex = 0.9,
        space = "bottom"
      ),
      
      key.footer = "(m/s)",
      
      # Ajustes de márgenes y fuentes
      par.settings = list(
        layout.heights = list(
          bottom.padding = 4, 
          sub = 4
        ),
        fontsize = list(text = 9, points = 9)
      )
    )
  }
}

# --- 4) Bucle de Generación (Thumb + Full) ---
png_thumb_list <- list()
png_full_list <- list()
cat("Generando", nrow(stations_info), "pares de rosas de viento en", output_dir, "...\n")

for (i in seq_len(nrow(stations_info))) {
  id      <- stations_info$nro[i]
  st_name <- stations_info$nombre[i]
  
  stn_dt <- df %>%
    filter(nro == id, is.finite(ff), is.finite(dd)) %>%
    select(ff, dd)
  
  if (!nrow(stn_dt)) next
  
  clean_name <- janitor::make_clean_names(st_name)
  f_thumb <- file.path(output_dir, sprintf("%s_thumb.png", clean_name))
  f_full  <- file.path(output_dir, sprintf("%s_full.png",  clean_name))
  
  # Generar PNG miniatura (pequeño y limpio)
  make_wind_rose_png(stn_dt, st_name, f_thumb, width = 100, height = 100, res = 72, is_thumb = TRUE)
  # Generar PNG completo (MÁS GRANDE: 400x400 para que entren títulos)
  make_wind_rose_png(stn_dt, st_name, f_full,  width = 400, height = 400, res = 96, is_thumb = FALSE)
  
  png_thumb_list[[as.character(id)]] <- f_thumb
  png_full_list[[as.character(id)]] <- f_full
}

# --- 5) Combinar y Codificar en Base64 ---
thumb_df <- tibble(nro_str = names(png_thumb_list), png_thumb = unlist(png_thumb_list))
full_df  <- tibble(nro_str = names(png_full_list),  png_full  = unlist(png_full_list))

map_data <- stations_info %>%
  mutate(nro_str = as.character(nro)) %>%
  left_join(thumb_df, by = "nro_str") %>%
  left_join(full_df,  by = "nro_str") %>%
  select(-nro_str) %>%
  filter(file.exists(png_thumb) & file.exists(png_full))

stopifnot(nrow(map_data) > 0)

map_data <- map_data %>%
  rowwise() %>%
  mutate(
    png_thumb_data = base64enc::dataURI(file = png_thumb, mime = "image/png"),
    png_full_data  = base64enc::dataURI(file = png_full,  mime = "image/png")
  ) %>%
  ungroup()

# --- 6) Capa "resumen" (Círculos) ---
# (CORREGIDO: 'lon' en lugar de 'Lo')
stations_speed <- df %>%
  filter(is.finite(ff)) %>%
  group_by(nro) %>%
  summarise(
    ws_mean = mean(ff, na.rm = TRUE),
    lat = first(latitud), 
    lon = first(longitud), # <-- ¡Error corregido!
    nombre = first(nombre), 
    provincia = first(provincia),
    .groups = 'drop'
  ) %>%
  filter(is.finite(lat) & is.finite(lon))

pal_ws <- leaflet::colorNumeric("YlGnBu", domain = stations_speed$ws_mean)

# --- 7) Mapa Leaflet (Clúster + Popup) ---
icon_rose <- leaflet::icons(
  iconUrl   = map_data$png_thumb_data, # El ícono es la miniatura limpia
  iconWidth = 72, iconHeight = 72,      # Tamaño que permite la expansión (spiderfy)
  className = "rose"
)

m <- leaflet::leaflet() %>%
  leaflet::addProviderTiles(leaflet::providers$CartoDB.Positron) %>%
  
  # Capa Resumen (círculos)
  leaflet::addCircleMarkers(
    data = stations_speed, group = "Resumen (círculos)",
    lng = ~lon, lat = ~lat, radius = 6,
    fillColor = ~pal_ws(ws_mean), fillOpacity = 0.8, stroke = FALSE,
    popup = ~paste0("<b>", nombre, "</b><br>", provincia,
                    "<br>Viento medio: ", round(ws_mean, 1), " m/s")
  ) %>%
  leaflet::addLegend("bottomright", pal = pal_ws, values = stations_speed$ws_mean,
                     title = "Velocidad del viento (m/s)", group = "Resumen (círculos)") %>%
  
  # Capa Detalle (rosas)
  leaflet::addMarkers(
    data = map_data, group = "Rosas (detalle)",
    lng = ~lon, lat = ~lat, icon = icon_rose, # Ícono = thumb
    
    # Popup = full
    popup = ~sprintf("<b>Estacionalidad (%s)</b><br><img src='%s' width='280'>", 
                     nombre, png_full_data),
    
    options = leaflet::markerOptions(zIndexOffset = 1000),
    clusterOptions = leaflet::markerClusterOptions(
      disableClusteringAtZoom = 8, 
      spiderfyOnMaxZoom = TRUE,     # La expansión FUNCIONARÁ
      showCoverageOnHover = FALSE,
      maxClusterRadius = 60
    )
  ) %>%
  leaflet::addLayersControl(
    overlayGroups = c("Resumen (círculos)", "Rosas (detalle)"),
    options = leaflet::layersControlOptions(collapsed = FALSE)
  ) %>%
  leaflet::hideGroup("Rosas (detalle)")

# Mostrar el mapa
m

9. Dispersión y Modelado

9.1. Dispersión entre Estaciones

Este “gráfico de cinta” (ribbon plot) muestra la serie temporal nacional, pero en lugar de solo la media, grafica la mediana (P50) y el rango del 80% (P10-P90) de todas las estaciones.

La cinta (área sombreada) representa la dispersión o variabilidad entre las diferentes regiones del país. Se puede ver que la variabilidad es mayor en invierno (cinta más ancha).

# Ribbons de percentiles nacionales (dispersión entre estaciones)
nat_percentiles <- monthly_by_station_fair %>%
  group_by(ym) %>%
  summarise(
    # La columna 'y' contiene la temperatura media mensual por estación
    p10 = quantile(y, 0.10, na.rm = TRUE),
    p50 = quantile(y, 0.50, na.rm = TRUE),
    p90 = quantile(y, 0.90, na.rm = TRUE),
    n_est = n(), 
    .groups = 'drop'
  ) %>%
  # Ordenar por fecha
  arrange(ym)

p_ribbon <- plot_ly(nat_percentiles, x=~ym) %>%
    
    # 1. Banda de Variabilidad (p10-p90)
    add_ribbons(ymin = ~p10, ymax = ~p90, 
                name = "Rango 80% (p10–p90)", 
                opacity = 0.25,
                line = list(color = 'transparent'), 
                fillcolor = 'lightblue') %>% 
    
    # 2. Línea Central (Mediana)
    add_trace(y=~p50, name="Mediana (P50)", mode="lines",
              line = list(color = '#1f77b4', width = 2)) %>%
    
    # --- Layout Final Integrando el Análisis de Sesgo ---
    layout(
        # Título: El título principal se mantiene descriptivo.
        title = list(text = "Temperatura nacional: un ciclo estacional constante con alta amplitud anual<br>
        <sup><b>La mediana (P50) se acerca al límite superior (P90) en verano, indicando un fuerte sesgo hacia condiciones cálidas en la mayoría de las estaciones.</b></sup>"),
        
        # Eje X 
        xaxis = list(
            title = "Fecha",
            dtick = "M12", 
            tickformat = "%b %Y"
        ),
        
        # Eje Y
        yaxis = list(title="Temperatura Mensual (°C)"), 
        
        # Leyenda
        legend = list(title = list(text = '<b>Métricas</b>'),
                      x = 100,
                      y = 1),
        
        # Fuente
        annotations = list(
            list(text = "Fuente: SMN – elaboración propia.",
                 showarrow = FALSE, xref = "paper", yref = "paper", x = 1, y = -0.15,
                 xanchor = 'right', yanchor = 'auto', font = list(size = 10))
        ),
        
        margin = list(b = 80, t = 80)
    )

p_ribbon

9.2. Modelado y Pronóstico (Prophet)

Como experimento final, intentamos predecir datos meteorológicos usando el modelo prophet de Facebook / Meta.

Primero, intentamos pronosticar la precipitación mensual para la estación con más datos (1991-2024). Entrenamos con datos hasta 2017 y probamos en 2018-adelante. El modelo captura la estacionalidad general.

# --- PASO 1: Cargar y Preparar los Datos ---
df_lluvia <- df_lluvia_con_info

# Limpiamos los datos
df_limpio <- df_lluvia %>%
  mutate(
    fecha = as.Date(fecha),
    precipitacion = as.numeric(precipitacion)
  )

# --- PASO 2: Encontrar la Mejor Estación (con más datos válidos) ---
estacion_con_mas_datos <- df_limpio %>%
  group_by(nombre) %>%
  summarise(
    registros_validos = sum(!is.na(precipitacion))
  ) %>%
  # Ordenamos de mayor a menor
  arrange(desc(registros_validos)) %>%
  # Elegimos la primera (la mejor)
  slice(1) %>%
  pull(nombre) # Extraemos el nombre

print(paste("Estación seleccionada:", estacion_con_mas_datos))

# --- PASO 3: Preparar Datos para Prophet (Filtrar y Agregar) ---
df_prophet <- df_limpio %>%
  filter(nombre == estacion_con_mas_datos) %>%
  mutate(mes_ano = floor_date(fecha, "month")) %>%
  group_by(mes_ano) %>%
  summarise(
    precipitacion_total = sum(precipitacion, na.rm = TRUE)
  ) %>%
  # Renombramos para que Prophet entienda
  rename(
    ds = mes_ano,
    y = precipitacion_total
  ) %>%
  ungroup()

# --- PASO 4: Dividir Datos (Train y Test) ---
# Train: 1991-01-01 hasta 2017-12-31
# Test: 2018-01-01 hasta el final

df_train <- df_prophet %>%
  filter(ds <= as.Date("2017-12-31"))

df_test <- df_prophet %>%
  filter(ds > as.Date("2017-12-31"))

print(paste("Registros de Entrenamiento:", nrow(df_train)))
print(paste("Registros de Prueba:", nrow(df_test)))

# --- PASO 5: Crear y Entrenar el Modelo Prophet ---

# Iniciamos Prophet.
# Prophet detectará automáticamente la estacionalidad anual (yearly.seasonality)
# ya que tenemos datos de varios años.
model <- prophet()

# Ajustamos el modelo (entrenamos)
model <- fit.prophet(model, df_train)

# --- PASO 6: Predecir y Evaluar ---
future_test <- df_test %>% select(ds)

# Realizamos la predicción
forecast <- predict(model, future_test)

# Unimos la predicción (forecast) con los valores reales (df_test)
# para poder comparar
df_comparacion <- forecast %>%
  select(ds, yhat, yhat_lower, yhat_upper) %>%
  left_join(df_test, by = "ds")

# --- PASO 7: Visualizar los Resultados ---

# 1. Gráfico completo de Prophet (Predicción vs Real)
# Esto creará un gráfico interactivo si estás en RStudio
plot_forecast <- plot(model, forecast)
plot_forecast <- plotly::ggplotly(plot_forecast) # <-- AÑADE ESTA LÍNEA
plot_forecast

# 2. Gráfico de los componentes del modelo
# Esto te mostrará la tendencia, la estacionalidad anual, etc.
plot_forecast_comp

Repetimos el proceso para la temperatura diaria, que es una señal mucho más estable. Entrenamos el modelo en la estación con más datos de temperatura hasta finales de 2023 y le pedimos que prediga el futuro (el “test set”).

# --- PASO 2: Encontrar la Mejor Estación (con más datos de TEMPERATURA) ---
estacion_con_mas_datos_temp <- df %>%
  filter(!is.na(temp)) %>% # Filtramos NAs de temperatura
  group_by(nombre) %>%
  summarise(registros_validos = n()) %>% # Contamos las filas restantes
  arrange(desc(registros_validos)) %>%
  slice(1) %>%
  pull(nombre) 

print(paste("Estación seleccionada (con más datos de temp):", estacion_con_mas_datos_temp))

# --- PASO 3: Preparar Datos para Prophet (AGREGACIÓN DIARIA) ---
df_prophet_diario <- df %>%
  filter(nombre == estacion_con_mas_datos_temp) %>%
  group_by(fecha) %>%
  summarise(
    temp_media_diaria = mean(temp, na.rm = TRUE)
  ) %>%
  rename(
    ds = fecha,
    y = temp_media_diaria
  ) %>%
  ungroup() %>%
  distinct() # Nos aseguramos de tener un solo valor por día

# --- PASO 4: Dividir Datos (Train y Test) ---
fecha_corte <- as.Date("2023-10-07")

df_train_diario <- df_prophet_diario %>%
  filter(ds <= fecha_corte)

df_test_diario <- df_prophet_diario %>%
  filter(ds > fecha_corte)

print(paste("Registros Diarios de Entrenamiento:", nrow(df_train_diario)))
print(paste("Registros Diarios de Prueba:", nrow(df_test_diario)))

# --- PASO 5: Crear y Entrenar el Modelo Prophet ---
# Prophet detectará automáticamente la estacionalidad ANUAL y SEMANAL
model_temp_diario <- prophet(weekly.seasonality = FALSE)

# Ajustamos el modelo (entrenamos)
model_temp_diario <- fit.prophet(model_temp_diario, df_train_diario)

# --- PASO 6: Predecir y Evaluar ---

# Creamos un dataframe "futuro"
future_test_diario <- df_test_diario %>% select(ds)

# Realizamos la predicción
forecast_temp_diario <- predict(model_temp_diario, future_test_diario)

# --- PASO 7: Visualizar los Resultados ---

# 1. Gráfico completo de Prophet (Predicción vs Real)
# Esto tardará un poquito más en generarse, ¡tiene muchos puntos!
p_main <- plot(model_temp_diario, forecast_temp_diario)
p_main <- plotly::ggplotly(p_main)
p_main
# 2. Gráfico de los componentes del modelo
# Ahora verás una estacionalidad "semanal" (weekly) además de la anual
p_comp

El gráfico de componentes (p_comp) descompone el pronóstico del modelo, mostrando la fuerte tendencia anual y la tendencia semanal (patrones de días de la semana).

El gráfico de comparación (pr_comp) muestra que la predicción del modelo (azul) sigue muy de cerca a los valores reales (negro) en el conjunto de prueba.

# --- PASO 8: Comparar Predicción Diaria vs. Realidad (Gráfico Final) ---

# 1. Unimos las predicciones con los datos reales del Test Set (Diario)
df_comparacion_diario <- forecast_temp_diario %>%
  select(ds, yhat, yhat_lower, yhat_upper) %>%
  left_join(df_test_diario, by = "ds") # 'df_test_diario' tiene el valor real 'y'

# 2. Graficamos la comparación usando ggplot2
# (Asegúrate de tener 'library(ggplot2)' cargada al inicio de tu Rmd)

pr_comp <- ggplot(df_comparacion_diario, aes(x = ds)) +
  
  # Línea Negra: Los valores REALES diarios que guardamos
  geom_line(aes(y = y, color = "Valor Real"), linewidth = 0.8) +
  
  # Línea Azul Punteada: La PREDICCIÓN diaria del modelo
  geom_line(aes(y = yhat, color = "Predicción"), linetype = "dashed", linewidth = 1) +
  
  # Área Gris de Incertidumbre
  geom_ribbon(aes(ymin = yhat_lower, ymax = yhat_upper), fill = "grey", alpha = 0.3) +
  
  # Títulos y colores
  scale_color_manual(values = c("Valor Real" = "black", "Predicción" = "dodgerblue")) +
  labs(title = "Comparación: Temperatura Diaria Real vs. Predicción (Test Set)",
       subtitle = "El modelo sigue bien la tendencia general y la estacionalidad.",
       y = "Temperatura Media Diaria (°C)",
       x = "Fecha",
       color = "Leyenda") +
  theme_minimal()
pr_comp <- plotly::ggplotly(pr_comp)
pr_comp

Calculamos el error numérico: - MAE (Error Absoluto Medio): En promedio, el modelo se equivoca por esta cantidad de grados. - RMSE (Raíz del Error Cuadrático Medio): Similar al MAE, pero penaliza más los errores grandes.

# --- PASO 9: Evaluación Numérica (MAE y RMSE) ---

# 1. Calculamos el error de cada día
df_errores <- df_comparacion_diario %>%
  mutate(
    error = y - yhat,           # Error (diferencia simple)
    error_absoluto = abs(error) # Error absoluto (siempre positivo)
  )

# 2. Calculamos el MAE (Mean Absolute Error)
# Es el promedio de los errores absolutos
mae <- mean(df_errores$error_absoluto, na.rm = TRUE)

print(paste("MAE (Error Absoluto Medio):", round(mae, 2), "grados"))
print("Esto significa que, en promedio, el modelo se equivocó por esta cantidad de grados.")

# 3. Calculamos el RMSE (Root Mean Squared Error)
# Es la raíz cuadrada del promedio de los errores al cuadrado
rmse <- sqrt(mean(df_errores$error^2, na.rm = TRUE))

print(paste("RMSE (Raíz del Error Cuadrático Medio):", round(rmse, 2), "grados"))
print("Similar al MAE, pero penaliza más los errores grandes.")

Finalmente, analizamos los residuos (los errores del modelo). El gráfico de residuos en el tiempo (p_res) debe parecer ruido aleatorio centrado en 0.

# --- PASO 10 (A): Gráfico de Residuos a lo largo del tiempo ---

# df_errores ya tiene la columna 'error' (y - yhat) y 'ds' (fecha)
p_res <- ggplot(df_errores, aes(x = ds, y = error)) +
  geom_line(color = "red", alpha = 0.8) +
  
  # Línea de referencia en 0 (el error perfecto)
  geom_hline(yintercept = 0, linetype = "dashed", color = "blue", linewidth = 1) +
  
  labs(title = "Residuos (Errores) a lo largo del tiempo",
       subtitle = "Idealmente: Ruido aleatorio centrado en 0",
       y = "Error (Real - Predicción en °C)",
       x = "Fecha") +
  theme_minimal()
p_res <- plotly::ggplotly(p_res)
p_res
# --- PASO 10 (B): Histograma de Residuos ---

hist_res <- ggplot(df_errores, aes(x = error)) +
  
  # Histograma con un color de relleno más agradable
  geom_histogram(aes(y = ..density..), 
                 bins = 40, # Aumentamos un poco los bins
                 fill = "deepskyblue3", 
                 color = "white", # Borde blanco para separar las barras
                 alpha = 0.8) +
  
  # Curva de densidad para ver la forma general
  geom_density(color = "black", linewidth = 1.2, alpha = 0.9) +
  
  # Línea vertical en Cero (Error Perfecto)
  geom_vline(xintercept = 0, 
             color = "black", 
             linetype = "dashed", 
             linewidth = 1.2) +
  
  # Líneas verticales para el Error Absoluto Medio (MAE)
  # Esto nos da un contexto de qué tan grande es el error promedio
  geom_vline(xintercept = mae, 
             color = "red", 
             linetype = "dotted", 
             linewidth = 1) +
  geom_vline(xintercept = -mae, 
             color = "red", 
             linetype = "dotted", 
             linewidth = 1) +
  
  # Títulos y etiquetas mejorados
  labs(
    title = "Distribución de Errores de Predicción",
    subtitle = paste0("La mayoría de los errores se centran en 0.\nLas líneas rojas punteadas marcan el Error Absoluto Medio (MAE: +/- ", round(mae, 2), "°)"),
    x = "Error de Predicción (Grados °C)",
    y = "Densidad"
  ) +
  
  # Un tema limpio
  theme_minimal()
hist_res <- plotly::ggplotly(hist_res)
hist_res

El histograma de residuos (hist_res) muestra que la gran mayoría de los errores son muy cercanos a cero, con algunos errores más grandes en las colas. Esto confirma un buen ajuste del modelo.

10. Salida de Datos y Gráficos

Descarga de dataframes para construir graficos en la shiny app

En este bloque final, preparamos los datos para ser consumidos por una aplicación externa (como una Shiny app). Normalizamos los dataframes clave (como feat_month, daily_station) y los guardamos en formato .Rds.

# --- 0) Normalización de df ---------------------------------------------------
# df ya existe en memoria
df <- as.data.table(df)
setnames(df, make_clean_names(names(df)))

# Tipos (sin :=, usando set())
# fecha
df[, fecha_chr := as.character(fecha)]
set(df, j = "fecha", value = as.Date(df[["fecha_chr"]]))
df[, fecha_chr := NULL]

# hora, nro
set(df, j = "hora", value = as.integer(df[["hora"]]))
set(df, j = "nro",  value = as.integer(df[["nro"]]))

# lat/long
set(df, j = "latitud",  value = as.numeric(df[["latitud"]]))
set(df, j = "longitud", value = as.numeric(df[["longitud"]]))

# datetime local BA
set(df, j = "datetime",
    value = as.POSIXct(paste(df[["fecha"]], sprintf("%02d:00:00", df[["hora"]])),
                       tz = "America/Argentina/Buenos_Aires"))

.save_rds(df, "data/df_clean")
.save_rds(daily_station, "data/daily_station")
.save_rds(monthly_by_station, "data/monthly_by_station")

# --- 1) GDD mensual (base 10°C) ----------------------------------------------
gdd_daily <- df[!is.na(temp),
                .(gdd = sum(pmax(0, temp - 10))/24),
                by = .(nro, ymd = as.Date(datetime))]
gdd_month <- gdd_daily[, .(gdd_m = sum(gdd)),
                       by = .(nro, ym = as.Date(format(ymd, "%Y-%m-01")))]

.save_rds(gdd_month, "data/gdd_month")

# --- 2) Join de estaciones (Nro -> Nombre) -----------------------------------
suppressPackageStartupMessages({
  library(data.table); library(janitor)
})

# 2.1 Cargar CSV y normalizar nombres
est_csv <- fread("smn_estaciones.csv", encoding = "UTF-8")
setnames(est_csv, make_clean_names(names(est_csv)))  # 'Nro'->'nro', 'Nombre'->'nombre'

# 2.2 Tipos y validaciones
if (!"nro" %in% names(est_csv)) stop("El CSV no tiene columna 'Nro' (o 'nro' tras normalizar).")
if (!"nombre" %in% names(est_csv)) stop("El CSV no tiene columna 'Nombre' (o 'nombre' tras normalizar).")
est_csv[, nro := as.integer(nro)]
est_csv[, nombre := as.character(nombre)]

# 2.3 Resolver duplicados por nro: quedarnos con el nombre más frecuente
est_map <- est_csv[!is.na(nro) & !is.na(nombre),
                   .N, by = .(nro, nombre)][order(nro, -N)][, .SD[1], by = nro][, N := NULL]

# 2.4 Asegurar tipos en gdd_month y agregar nombre_ref
stopifnot(exists("gdd_month"))
setnames(gdd_month, make_clean_names(names(gdd_month)))
if (!"nro" %in% names(gdd_month)) stop("gdd_month no tiene columna 'nro'.")  # corregido el typo
gdd_month[, nro := as.integer(nro)]

gdd_month_joined <- merge(
  gdd_month,
  est_map[, .(nro, nombre_ref = nombre)],
  by = "nro", all.x = TRUE
)

# --- 3) Preparar monthly_by_station (tiene nombre, ds, y) --------------------
monthly_by_station <- as.data.table(monthly_by_station)
setnames(monthly_by_station, make_clean_names(names(monthly_by_station)))  # nombre, ds, y

# Validaciones mínimas
if (!all(c("nombre","ds") %in% names(monthly_by_station))) {
  stop("monthly_by_station debe tener columnas 'nombre' y 'ds'.")
}
monthly_by_station[, nombre := as.character(nombre)]
monthly_by_station[, ds := as.character(ds)]

# ds puede venir como "YYYY-MM" o "YYYY-MM-DD": normalizo y creo ym = primer día de mes
monthly_by_station[, ds := ifelse(grepl("^\\d{4}-\\d{2}$", ds), paste0(ds, "-01"), ds)]
monthly_by_station[, ym := as.Date(ds)]
monthly_by_station[, ym := as.Date(format(ym, "%Y-%m-01"))]

# --- 4) Agregar GDD por nombre + mes -----------------------------------------
# Normalizo gdd_month_joined y agrego por nombre_ref + ym
setnames(gdd_month_joined, make_clean_names(names(gdd_month_joined)))
if (!all(c("nombre_ref","ym","gdd_m") %in% names(gdd_month_joined))) {
  stop("gdd_month_joined debe tener 'nombre_ref', 'ym' y 'gdd_m'.")
}
gdd_month_joined[, nombre_ref := as.character(nombre_ref)]
gdd_month_joined[, ym := as.Date(ym)]

# Si existiera más de un nro con el mismo nombre en un mes, sumo gdd_m
gdd_by_nombre <- gdd_month_joined[!is.na(nombre_ref) & !is.na(ym),
                                  .(gdd_m = sum(gdd_m, na.rm = TRUE)),
                                  by = .(nombre = nombre_ref, ym)]

# --- 5) Features integradas: merge por nombre + ym ---------------------------
feat_month <- merge(
  monthly_by_station,
  gdd_by_nombre,
  by = c("nombre","ym"),
  all.x = TRUE
)

# Orden sugerido si esas columnas existen
cols_target <- c("nombre","ym","y","ds","gdd_m","latitud","longitud","provincia",
                 "temp_m","hum_m","ff_m","pnm_m","pp_m","ndays")
setcolorder(feat_month, c(intersect(cols_target, names(feat_month)),
                          setdiff(names(feat_month), cols_target)))

.save_rds(feat_month, "data/feat_month")

# --- 6) Choices básicos para la app ------------------------------------------
estaciones <- sort(unique(na.omit(feat_month$nombre)))
anios      <- sort(unique(year(feat_month$ym)))  # años presentes en los datos mensuales
.save_rds(estaciones, "data/estaciones")
.save_rds(anios,      "data/anios")

message("=== Listo. Merge por nombre + ym; sin requerir nro en monthly_by_station ===")

Descarga de gráficos en memoria para exponer

Por último, definimos una función de ayuda (save_all_plots) que recorre el entorno de R, encuentra todos los objetos de gráficos (ggplot, plotly, leaflet, trellis) que creamos durante este análisis y los guarda automáticamente en disco (como PNG, PDF, HTML) en la carpeta figs/.

Esto nos permite exportar fácilmente todas las visualizaciones en la propia shiny app.

save_all_plots <- function(
  dir_out = "figs",
  formats = c("png","pdf","svg"),
  width = 7, height = 5, dpi = 300,
  env = .GlobalEnv
) {
  dir.create(dir_out, recursive = TRUE, showWarnings = FALSE)

  objs <- ls(envir = env, all.names = TRUE)
  n_saved <- 0

  for (nm in objs) {
    obj <- get(nm, envir = env)

    # --- ggplot2 --------------------------------------------------------------
    if (inherits(obj, "ggplot")) {
      for (fmt in formats) {
        f <- file.path(dir_out, paste0(nm, ".", fmt))
        try({
          ggplot2::ggsave(
            filename = f, plot = obj,
            width = width, height = height, dpi = dpi, units = "in", device = fmt
          )
          n_saved <- n_saved + 1
        }, silent = TRUE)
      }
      next
    }

    # --- htmlwidgets: plotly/leaflet/highcharter ------------------------------
    if (inherits(obj, "htmlwidget")) {
      f <- file.path(dir_out, paste0(nm, ".html"))
      try({
        htmlwidgets::saveWidget(obj, file = f, selfcontained = TRUE)
        n_saved <- n_saved + 1
      }, silent = TRUE)
      next
    }

    # --- lattice/trellis ------------------------------------------------------
    if (inherits(obj, "trellis")) {
      for (fmt in formats) {
        f <- file.path(dir_out, paste0(nm, ".", fmt))
        try({
          switch(fmt,
            png = {
              png(f, width = width, height = height, units = "in", res = dpi)
              print(obj); dev.off()
            },
            pdf = {
              pdf(f, width = width, height = height)
              print(obj); dev.off()
            },
            svg = {
              svg(f, width = width, height = height)
              print(obj); dev.off()
            },
            { } # otros formatos se pueden agregar acá
          )
          n_saved <- n_saved + 1
        }, silent = TRUE)
      }
      next
    }

    # --- gráficos base guardados con recordPlot() -----------------------------
    if (inherits(obj, "recordedplot")) {
      f_png <- file.path(dir_out, paste0(nm, ".png"))
      try({
        png(f_png, width = width, height = height, units = "in", res = dpi)
        replayPlot(obj)
        dev.off()
        n_saved <- n_saved + 1
      }, silent = TRUE)
      next
    }
  }

  message(sprintf("Listo: guardados %d archivos en '%s'.", n_saved, dir_out))
}

# Ejecutá:
save_all_plots(dir_out = "figs", formats = c("png","pdf","svg"), width = 7, height = 5, dpi = 300)
LS0tDQp0aXRsZTogIkVEQTogRGF0b3MgbWV0ZXJlb2zDs2dpY29zIHBvciBlc3RhY2lvbmVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCiMgSW50cm9kdWNjacOzbg0KRXN0ZSBkb2N1bWVudG8gcHJlc2VudGEgdW4gQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyBkZSBEYXRvcyAoRURBKSBzb2JyZSB1biBjb25qdW50byBkZSBkYXRvcyBtZXRlb3JvbMOzZ2ljb3MgaG9yYXJpb3MgZGUgbcO6bHRpcGxlcyBlc3RhY2lvbmVzIGVuIEFyZ2VudGluYS4gRWwgb2JqZXRpdm8gZXMgbGltcGlhciwgcHJvY2VzYXIgeSB0cmFuc2Zvcm1hciBsb3MgZGF0b3MgYnJ1dG9zIHBhcmEgZGVzY3VicmlyIHBhdHJvbmVzLCBhbmFsaXphciB0ZW5kZW5jaWFzIHRlbXBvcmFsZXMgeSBnZW9lc3BhY2lhbGVzLCBlIGlkZW50aWZpY2FyIGV2ZW50b3MgZXh0cmVtb3MuDQoNCkVsIGFuw6FsaXNpcyBjb21pZW56YSBjb24gdW5hIGZhc2UgZGUgaW5nZXN0YSB5IGxpbXBpZXphIGRlIGRhdG9zLCBzZWd1aWRhIGRlIGxhIGluZ2VuaWVyw61hIGRlIHZhcmlhYmxlcyAoY29tbyBsYSBkZXNhZ3JlZ2FjacOzbiBkZSBwcmVjaXBpdGFjacOzbiB5IGxhIGNhdGVnb3JpemFjacOzbiBkZSB2aWVudG9zKSwgeSBjb25jbHV5ZSBjb24gdW5hIHNlcmllIGRlIHZpc3VhbGl6YWNpb25lcyBpbnRlcmFjdGl2YXMgeSB1biBtb2RlbGFkbyBwcmVkaWN0aXZvIGLDoXNpY28NCg0KIyAxLiBDb25maWd1cmFjacOzbiBlIEluZ2VzdGEgZGUgRGF0b3MNCg0KUHJpbWVybywgY2FyZ2Ftb3MgdG9kYXMgbGFzIGxpYnJlcsOtYXMgbmVjZXNhcmlhcyBwYXJhIGVsIGFuw6FsaXNpcywgaW5jbHV5ZW5kbyB0aWR5dmVyc2UgcGFyYSBsYSBtYW5pcHVsYWNpw7NuIGRlIGRhdG9zLCBsZWFmbGV0IHBhcmEgbWFwYXMsIHkgcHJvcGhldCBwYXJhIHNlcmllcyB0ZW1wb3JhbGVzLg0KYGBge3J9DQpybShsaXN0ID0gbHMoKSkNCm9wdGlvbnMoc2NpcGVuPTk5OSkNCg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNsaWRlcikNCmxpYnJhcnkoInJzdHVkaW9hcGkiKQ0KbGlicmFyeShhcnJvdykNCmxpYnJhcnkoem9vKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShqYW5pdG9yKQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHByb3BoZXQpDQpsaWJyYXJ5KGh0bWx3aWRnZXRzKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KHZpcmlkaXNMaXRlKQ0KbGlicmFyeShwbmcpDQppZiAoIXJlcXVpcmVOYW1lc3BhY2UoIm9wZW5haXIiLCBxdWlldGx5ID0gVFJVRSkpIHsNCiAgaW5zdGFsbC5wYWNrYWdlcygib3BlbmFpciIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQp9DQpsaWJyYXJ5KG9wZW5haXIpDQpsaWJyYXJ5KGJhc2U2NGVuYykNCmxpYnJhcnkodGliYmxlKSAgIA0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KHN0cmluZ2kpDQoNCnNldHdkKGRpcm5hbWUocnN0dWRpb2FwaTo6Z2V0QWN0aXZlRG9jdW1lbnRDb250ZXh0KCkkcGF0aCkpDQpgYGANCg0KTm8gdGVuZW1vcyBlbCBkYXRhc2V0IGNvbXBsZXRvLiBIYXkgcXVlIGFybWFybG8uIFByaW1lcm8gZW1wZXphbW9zIHVuaWVuZG8gbG9zIGFyY2hpdm9zIGRlIHRleHRvLCBxdWUgZXN0YW4gc2VwYXJhZG8gcG9yIGVzcGFjaW9zIHRhYnVsYWRvcy4gSWRlbnRpZmljYW1vcyBjYWRhIGNvbHVtbmEgY29uIGV4cHJlc2lvbmVzIHJlZ3VsYXJlcy4gIEx1ZWdvLCBjb252ZXJ0aW1vcyBsYXMgY29sdW1uYXMgZGUgdGV4dG8gYSBzdXMgdGlwb3MgZGUgZGF0b3MgY29ycmVjdG9zIChudW3DqXJpY28sIGZlY2hhKSB5IGd1YXJkYW1vcyBlbCByZXN1bHRhZG8gZW4gZGF0YXNldF9jbGltYV91bmlmaWNhZG8uUmRzIHBhcmEgbm8gdGVuZXIgcXVlIHJlcGV0aXIgZXN0ZSBjb3N0b3NvIHByb2Nlc28uDQoNCmBgYHtyfQ0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBQQVNPIDI6IERlZmluaXIgbGEgdWJpY2FjacOzbiBkZSB0dXMgYXJjaGl2b3MNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgVXNhICIuIiBzaSB5YSBlc3RhYmxlY2lzdGUgZWwgZGlyZWN0b3JpbyBkZSB0cmFiYWpvIChyZWNvbWVuZGFkbykNCnJ1dGFfY2FycGV0YSA8LSAic21uLWRhdGEvIg0KDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIFBBU08gMzogTGEgZnVuY2nDs24gZGUgcHJvY2VzYW1pZW50byAoUkVHRVggSElQRVItUkVGSU5BRE8pDQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpwcm9jZXNhcl9hcmNoaXZvX3JlZ2V4X2dlbmVyYWwgPC0gZnVuY3Rpb24ocnV0YV9hcmNoaXZvKSB7DQogIA0KICBwcmludChwYXN0ZSgiUHJvY2VzYW5kbzoiLCBydXRhX2FyY2hpdm8pKSAjIFByb2dyZXNvDQogIA0KICB0cnlDYXRjaCh7DQogICAgDQogICAgIyAtLS0gMS4gTGVlciBsw61uZWFzIChsYXRpbjEpIC0tLQ0KICAgIGxpbmVhcyA8LSByZWFkcjo6cmVhZF9saW5lcyggcnV0YV9hcmNoaXZvLCBza2lwX2VtcHR5X3Jvd3MgPSBUUlVFLCBsb2NhbGUgPSBsb2NhbGUoZW5jb2RpbmcgPSAibGF0aW4xIikgKQ0KICAgIA0KICAgICMgLS0tIDIuIE9taXRpciBhcmNoaXZvcyB2YWPDrW9zIC8gc29sbyBlbmNhYmV6YWRvIC0tLQ0KICAgIGlmIChpcy5udWxsKGxpbmVhcykgfHwgbGVuZ3RoKGxpbmVhcykgPD0gMikgeyByZXR1cm4oTlVMTCkgfQ0KICAgIGxpbmVhc19kYXRvc19yYXcgPC0gbGluZWFzWy0oMToyKV0NCiAgICBpZiAobGVuZ3RoKGxpbmVhc19kYXRvc19yYXcpID09IDApIHsgcmV0dXJuKE5VTEwpIH0NCiAgICANCiAgICAjIC0tLSAzLiBMw7NnaWNhIGRlICJDb3NpZG8iIC0tLQ0KICAgIGxpbmVhc19jb3NpZGFzIDwtIGMoKTsgYnVmZmVyX2xpbmVhIDwtICIiDQogICAgZm9yIChsaW5lYV9hY3R1YWwgaW4gbGluZWFzX2RhdG9zX3Jhdykgew0KICAgICAgaWYgKG5jaGFyKHRyaW13cyhsaW5lYV9hY3R1YWwpKSA9PSAwKSB7IG5leHQgfQ0KICAgICAgaWYgKHN0cmluZ3I6OnN0cl9zdGFydHModHJpbXdzKGxpbmVhX2FjdHVhbCksICJeXFxkezh9IikpIHsNCiAgICAgICAgaWYgKG5jaGFyKGJ1ZmZlcl9saW5lYSkgPiAwKSB7IGxpbmVhc19jb3NpZGFzIDwtIGMobGluZWFzX2Nvc2lkYXMsIGJ1ZmZlcl9saW5lYSkgfQ0KICAgICAgICBidWZmZXJfbGluZWEgPC0gbGluZWFfYWN0dWFsDQogICAgICB9IGVsc2UgeyBidWZmZXJfbGluZWEgPC0gcGFzdGUwKGJ1ZmZlcl9saW5lYSwgbGluZWFfYWN0dWFsKSB9DQogICAgfQ0KICAgIGlmIChuY2hhcihidWZmZXJfbGluZWEpID4gMCkgeyBsaW5lYXNfY29zaWRhcyA8LSBjKGxpbmVhc19jb3NpZGFzLCBidWZmZXJfbGluZWEpIH0NCiAgICANCiAgICAjIC0tLSA0LiBFeHRyYWNjacOzbiBjb24gRXhwcmVzaW9uZXMgUmVndWxhcmVzIChISVBFUi1SRUZJTkFETykgLS0tDQogICAgIyBQYXRyw7NuIG3DoXMgZXNwZWPDrWZpY28gYmFzYWRvIGVuIHRpcG9zIGRlIGRhdG9zIGVzcGVyYWRvczoNCiAgICBwYXRyb25fZXNwZWNpZmljbyA8LSBwYXN0ZTAoDQogICAgICAiXiIsICAgICAgICAgICAgICAgICMgSW5pY2lvIGRlIGzDrW5lYQ0KICAgICAgIihcXGR7OH0pIiwgICAgICAgICAjIEdydXBvIDE6IEZFQ0hBICg4IGTDrWdpdG9zIGV4YWN0b3MpDQogICAgICAiXFxzKyIsICAgICAgICAgICAgICMgU2VwYXJhZG9yDQogICAgICAiKFxcZCt8XFxzezEsfSkiLCAgICMgR3J1cG8gMjogSE9SQSAobsO6bWVyb3MgTyBlc3BhY2lvcykNCiAgICAgICJcXHMrIiwgICAgICAgICAgICAgIyBTZXBhcmFkb3INCiAgICAgICIoWy0rXT9cXGQqXFwuP1xcZCt8XFxzezEsfSkiLCAjIEdydXBvIDM6IFRFTVAgKG7Dum1lcm8gY29uIGRlY2ltYWwgb3BjaW9uYWwgTyBlc3BhY2lvcykNCiAgICAgICJcXHMrIiwgICAgICAgICAgICAgIyBTZXBhcmFkb3INCiAgICAgICIoXFxkK3xcXHN7MSx9KSIsICAgIyBHcnVwbyA0OiBIVU0gKG7Dum1lcm9zIE8gZXNwYWNpb3MpDQogICAgICAiXFxzKyIsICAgICAgICAgICAgICMgU2VwYXJhZG9yDQogICAgICAjIEdydXBvIDU6IFBOTSAobsO6bWVybyBjb24gZGVjaW1hbCBvcGNpb25hbCBPIEFMIE1FTk9TIERPUyBlc3BhY2lvcykNCiAgICAgICIoWy0rXT9cXGQrXFwuXFxkK3xcXHN7Mix9KSIsDQogICAgICAiXFxzKyIsICAgICAgICAgICAgICMgU2VwYXJhZG9yDQogICAgICAiKFxcZCt8XFxzezEsfSkiLCAgICMgR3J1cG8gNjogREQgKFNPTE8gbsO6bWVyb3MgTyBlc3BhY2lvcykNCiAgICAgICJcXHMrIiwgICAgICAgICAgICAgIyBTZXBhcmFkb3INCiAgICAgICIoXFxkK3xcXHN7MSx9KSIsICAgIyBHcnVwbyA3OiBGRiAoU09MTyBuw7ptZXJvcyBPIGVzcGFjaW9zKQ0KICAgICAgIlxccysiLCAgICAgICAgICAgICAjIFNlcGFyYWRvcg0KICAgICAgIiguKj8pIiwgICAgICAgICAgICAjIEdydXBvIDg6IE5PTUJSRSAoZWwgcmVzdG8sIG5vIGdvbG9zbykNCiAgICAgICJcXHMqJCIgICAgICAgICAgICAgIyBFc3BhY2lvcyBvcGNpb25hbGVzIGFsIGZpbmFsDQogICAgKQ0KICAgIA0KICAgIG1hdGNoZXMgPC0gc3RyX21hdGNoKGxpbmVhc19jb3NpZGFzLCBwYXRyb25fZXNwZWNpZmljbykNCiAgICANCiAgICAjIFZlcmlmaWNhY2nDs24NCiAgICBpZiAoaXMubnVsbChtYXRjaGVzKSB8fCBuY29sKG1hdGNoZXMpICE9IDkgfHwgYWxsKGlzLm5hKG1hdGNoZXNbLDJdKSkpIHsNCiAgICAgIHdhcm5pbmcocGFzdGUoIlJlZ2V4IGVzcGVjw61maWNvIGZhbGzDsyBlbjoiLCBydXRhX2FyY2hpdm8sICItIE9taXRpZW5kby4iKSkNCiAgICAgICMgUG9kcsOtYW1vcyBhw7FhZGlyIHVuIGZhbGxiYWNrIGFxdcOtIHNpIGZ1ZXJhIG5lY2VzYXJpbywgcGVybyBjb25maWVtb3MgZW4gZXN0ZSBwYXRyw7NuDQogICAgICByZXR1cm4oTlVMTCkNCiAgICB9DQogICAgDQogICAgIyBDb252ZXJ0aXIgYSBkYXRhIGZyYW1lDQogICAgZGF0b3NfZXh0cmFpZG9zIDwtIGFzLmRhdGEuZnJhbWUobWF0Y2hlc1ssIC0xLCBkcm9wID0gRkFMU0VdLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQogICAgY29sbmFtZXMoZGF0b3NfZXh0cmFpZG9zKSA8LSBjKCJGRUNIQSIsICJIT1JBIiwgIlRFTVAiLCAiSFVNIiwgIlBOTSIsICJERCIsICJGRiIsICJOT01CUkUiKQ0KICAgIGRhdG9zX2V4dHJhaWRvcyA8LSBkYXRvc19leHRyYWlkb3MgJT4lIG11dGF0ZShhY3Jvc3MoZXZlcnl0aGluZygpLCB0cmltd3MpKQ0KICAgIA0KICAgIHJldHVybihkYXRvc19leHRyYWlkb3MpICMgRGV2b2x2ZXIgZGF0b3MgY29tbyB0ZXh0bw0KICAgIA0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICB3YXJuaW5nKHBhc3RlKCJFcnJvciBHUkFWRSBwcm9jZXNhbmRvIGNvbiBSZWdleDoiLCBydXRhX2FyY2hpdm8sICItIiwgZSRtZXNzYWdlKSkNCiAgICByZXR1cm4oTlVMTCkNCiAgfSkNCn0NCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgUEFTTyA1OiBFamVjdXRhciBlbCBwcm9jZXNvIHkgY29tYmluYXIgdG9kbw0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIDEuIE9idGVuZXIgbGEgbGlzdGEgZGUgbG9zIGFyY2hpdm9zIC50eHQNCmFyY2hpdm9zIDwtIGxpc3QuZmlsZXMocGF0aCA9IHJ1dGFfY2FycGV0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICJcXC50eHQkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgZnVsbC5uYW1lcyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgIGlnbm9yZS5jYXNlID0gVFJVRSkNCg0KcHJpbnQocGFzdGUoIlNlIGVuY29udHJhcm9uIiwgbGVuZ3RoKGFyY2hpdm9zKSwgImFyY2hpdm9zIHBhcmEgaW1wb3J0YXIuIikpDQoNCiMgMi4gQXBsaWNhciBsYSBmdW5jacOzbiBDT1JSRUNUQSBhIHRvZG9zIGxvcyBhcmNoaXZvcw0KIyAgICDCoUFTRUfDmlJBVEUgZGUgcXVlIGVsIG5vbWJyZSBhcXXDrSBjb2luY2lkYSBjb24gZWwgZGUgbGEgZnVuY2nDs24hDQpkYXRhc2V0X3RlbXBvcmFsIDwtIHB1cnJyOjptYXBfZGZyKGFyY2hpdm9zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9jZXNhcl9hcmNoaXZvX3JlZ2V4X2dlbmVyYWwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5pZCA9ICJhcmNoaXZvX29yaWdlbiIpDQoNCnByaW50KCJJbXBvcnRhY2nDs24gKGNvbW8gdGV4dG8pIGNvbXBsZXRhZGEuIikNCg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBQQVNPIDY6IENvbnZlcnRpciBjb2x1bW5hcyBhbCB0aXBvIGRlIGRhdG8gY29ycmVjdG8NCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnByaW50KCJDb252aXJ0aWVuZG8gdGlwb3MgZGUgZGF0b3MuLi4iKQ0KDQpkYXRhc2V0X2NvbXBsZXRvIDwtIGRhdGFzZXRfdGVtcG9yYWwgJT4lDQogICMgUHJpbWVybywgcmVlbXBsYXphciBjYWRlbmFzIHZhY8OtYXMgIiIgbyBxdWUgc29sbyBzZWFuIGVzcGFjaW9zIGNvbiBOQQ0KICBtdXRhdGUoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgfm5hX2lmKHRyaW13cyguKSwgIiIpKSkgJT4lDQogIG11dGF0ZSgNCiAgICAjIENvbnZlcnRpciBsYXMgY29sdW1uYXMgbnVtw6lyaWNhcw0KICAgIGFjcm9zcygNCiAgICAgIC5jb2xzID0gYyhIT1JBLCBURU1QLCBIVU0sIFBOTSwgREQsIEZGKSwNCiAgICAgIC5mbnMgPSBhcy5udW1lcmljICMgQ29udmllcnRlIGEgbsO6bWVybywgbG9zIE5BIHNlIG1hbnRpZW5lbg0KICAgICksDQogICAgDQogICAgIyBDb252ZXJ0aXIgbGEgRkVDSEENCiAgICBGRUNIQSA9IGFzLkRhdGUoRkVDSEEsIGZvcm1hdCA9ICIlZCVtJVkiKQ0KICAgIA0KICAgICMgTk9NQlJFIHlhIGZ1ZSBsaW1waWFkbyBjb24gdHJpbXdzIGRlbnRybyBkZSBsYSBmdW5jacOzbiB5IGNvbiBuYV9pZiBhcnJpYmENCiAgKQ0KDQpwcmludCgiwqFDb252ZXJzacOzbiBkZSBkYXRvcyBjb21wbGV0YWRhISIpDQoNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgUEFTTyA3OiBSZXZpc2FyIGVsIGRhdGFzZXQgZmluYWwNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KcHJpbnQocGFzdGUoIlRvdGFsIGRlIGZpbGFzIGltcG9ydGFkYXM6IiwgbnJvdyhkYXRhc2V0X2NvbXBsZXRvKSkpDQoNCnByaW50KCJSZXZpc2FuZG8gbGFzIHByaW1lcmFzIDEwIGZpbGFzIGRlbCBkYXRhc2V0IGZpbmFsOiIpDQpwcmludChoZWFkKGRhdGFzZXRfY29tcGxldG8sIDEwKSkNCg0KcHJpbnQoIlJldmlzYW5kbyBsYSBlc3RydWN0dXJhIGZpbmFsOiIpDQpzdHIoZGF0YXNldF9jb21wbGV0bykNCg0KcHJpbnQoIlJldmlzYW5kbyB1biByZXN1bWVuIGVzdGFkw61zdGljbyBiw6FzaWNvOiIpDQpzdW1tYXJ5KGRhdGFzZXRfY29tcGxldG8pDQoNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgUEFTTyA4OiBHdWFyZGFyIGVsIGRhdGFzZXQgZmluYWwg8J+Svg0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KcHJpbnQoIkd1YXJkYW5kbyBlbCBkYXRhc2V0IHVuaWZpY2FkbyBlbiAnZGF0YXNldF9jbGltYV91bmlmaWNhZG8uUmRzJy4uLiIpDQoNCnNhdmVSRFMoZGF0YXNldF9jb21wbGV0bywgZmlsZSA9ICJkYXRhc2V0X2NsaW1hX3VuaWZpY2Fkby5SZHMiKQ0KDQpwcmludCgiwqFEYXRhc2V0IGd1YXJkYWRvIGNvbiDDqXhpdG8hIExhIHByw7N4aW1hIHZleiwgc29sbyB1c2EgcmVhZFJEUygnZGF0YXNldF9jbGltYV91bmlmaWNhZG8uUmRzJykuIikNCg0KcmVhZHI6OndyaXRlX2NzdihkYXRhc2V0X2NvbXBsZXRvLCAiZGF0YXNldF9jbGltYV91bmlmaWNhZG8uY3N2IikNCmBgYA0KDQpQcmltZXIgY2hlY2twb2ludDogUGFyYSBubyBlamVjdXRhciB0b2RvIGxvIGRlIGFycmliYQ0KYGBge3J9DQpkYXRhc2V0X2NvbXBsZXRvIDwtIHJlYWRSRFMoImRhdGFzZXRfY2xpbWFfdW5pZmljYWRvLlJkcyIpDQpgYGANCg0KIyAyLiBMaW1waWV6YSBkZSBEYXRvcyB5IFRyYXRhbWllbnRvIGRlIE5Bcw0KVW4gcHJpbWVyIHJlc3VtZW4geSBjb250ZW8gZGUgdmFsb3JlcyBOQSByZXZlbGEgcXVlLCBhdW5xdWUgbG9zIGRhdG9zIGVzdMOhbiBsZcOtZG9zLCBubyBlc3TDoW4gbGltcGlvcy4gTcO6bHRpcGxlcyBjb2x1bW5hcyB0aWVuZW4gdW5hIGFsdGEgcHJvcG9yY2nDs24gZGUgdmFsb3JlcyBmYWx0YW50ZXMgbyBhbsOzbWFsb3MgcXVlIGRlYmVtb3MgdHJhdGFyLg0KYGBge3J9DQpkYXRhc2V0X21ldCA8LSBkYXRhc2V0X2NvbXBsZXRvDQoNCnN1bW1hcnkoZGF0YXNldF9tZXQpDQojIHByb3BvcmNpw7NuIGRlIHZhbG9yZXMgTkEgcGFyYSBlc3RhcyBjb2x1bW5hcw0KYXR0YWNoKGRhdGFzZXRfbWV0KSAjIGFkanVudGFuZG8gcGFyYSBldml0YXIgbWVuY2lvbmFyIGRmJHZhciBjYWRhIHZleg0KDQpjb2wxID0gYygpIA0KY29sMiA9IGMoKQ0KY29sMyA9IGMoKQ0KDQojIGJ1Y2xlIA0KZm9yIChpIGluICgxOmRpbShkYXRhc2V0X21ldClbMl0pKXsNCiAgY29sMVtpXSA9IGNvbG5hbWVzKGRhdGFzZXRfbWV0KVtpXQ0KICBjb2wyW2ldID0gIHN1bShpcy5uYShkYXRhc2V0X21ldFtpXSkpDQogIGNvbDNbaV0gPSByb3VuZChjb2wyW2ldL2RpbShkYXRhc2V0X21ldClbMV0sNCkNCn0NCg0KIyBjcmVhbmRvIHVuIGRhdGFmcmFtZSBwYXJhIGNvbXBpbGFyIGxvcyB2YWxvcmVzIE5BIA0KKGRmX05BIDwtIHRpYmJsZSgiQ29sdW1uYSIgPSBjb2wxLCAiVmFsb3Jlc19OQSIgPSBjb2wyLCAiUHJvcG9yY2lvbl9OQSIgPSBjb2wzKSkNCg0KYGBgDQoNCiMjIDIuMS4gTGltcGllemEgZGUgVGVtcGVyYXR1cmEgKFRFTVApDQoNCkNvbWVuemFtb3MgY29uIGxhIFRlbXBlcmF0dXJhLiBVbiBoaXN0b2dyYW1hIG5vcyBtdWVzdHJhIHF1ZSBsYSBtYXlvcsOtYSBkZSBsb3MgZGF0b3Mgc2UgY29uY2VudHJhbiBlbiB1biByYW5nbyBsw7NnaWNvLCBwZXJvIGV4aXN0ZW4gdmFsb3JlcyBhdMOtcGljb3MgZXh0cmVtb3MgKGVqLiA+IDUwwrBDIG8gPCAtNDDCsEMpIHF1ZSBzb24gZsOtc2ljYW1lbnRlIGltcG9zaWJsZXMuDQoNCkZpbHRyYW1vcyBlc3RvcyB2YWxvcmVzLCBjb252aXJ0acOpbmRvbG9zIGVuIE5BLg0KDQpgYGB7cn0NCnRpYmJsZShkYXRhc2V0X21ldCkNCnN1bW1hcnkoZGF0YXNldF9tZXQpDQoNCmdncGxvdChkYXRhc2V0X21ldCwgYWVzKHggPSBURU1QKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLTEwMCwgMTAwKSwgeWxpbSA9IGMoMCwgMSkpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJMYSB0ZW1wZXJhdHVyYSBzZSBjb25jZW50cmEgYXByb3hpbWFkYW1lbnRlIGVudHJlIGxvcyAtNDAgeSA1MCBncmFkb3MiLA0KICAgIHN1YnRpdGxlID0gIlRvZG9zIGxvcyBkZW3DoXMgdmFsb3JlcyBkZWJlbiBzZXIgZXJyw7NuZW9zIg0KICApDQoNCiMgUG9uZW1vcyBOQSBkb25kZSBsYSB0ZW1wZXJhdHVyYSBzZWEgb3RyYSBxdWUgZXNlIGludGVydmFsbw0KDQptZXRfbGltcGlvIDwtIGRhdGFzZXRfbWV0IHw+DQogIG11dGF0ZSgNCiAgICBURU1QID0gY2FzZV93aGVuKA0KICAgICAgVEVNUCA+IC00MCAmIFRFTVAgPCA1MCB+IFRFTVAgI1NpIGVzdGEgZW50cmUgZXNvcyB2YWxvcmVzIGVzIGxhIG1pc21hIHRlbXBlcmF0dXJhLCBzaW5vIHBvbmUgTkENCiAgICApDQogICkNCmBgYA0KDQpFc3RvIGluY3JlbWVudGEgbnVlc3Ryb3MgTkEgZW4gVGVtcGVyYXR1cmEsIHBvciBsbyBxdWUgZGViZW1vcyBpbXB1dGFybG9zLiBOdWVzdHJhIGVzdHJhdGVnaWEgZXMgaXRlcmF0aXZhIHkgc2UgYmFzYSBlbiBwcm9tZWRpb3MgbcOzdmlsZXM6DQotIFByaW1lcm8sIGludGVudGFtb3MgdW4gcHJvbWVkaW8gbcOzdmlsIGRlIDUgZMOtYXMgKDIgZMOtYXMgYW50ZXMsIDIgZGVzcHXDqXMpIGFncnVwYW5kbyBwb3IgZXN0YWNpw7NuIHkgaG9yYS4NCi0gUHJvYmFtb3MgdW5hIHNlZ3VuZGEgaW1wdXRhY2nDs24gY29uIHVuIHByb21lZGlvIG3Ds3ZpbCBkZSA1IGhvcmFzICgyIGhvcmFzIGFudGVzLCAyIGRlc3B1w6lzKSBhZ3J1cGFuZG8gcG9yIGVzdGFjacOzbiB5IGTDrWEuDQotIEVzdG8gcmVkdWNlIGxvcyBOQSBhIHVubyBzb2xvLiBJbnNwZWNjaW9uYW1vcyBlc3RlIGNhc28gKGVuIE9CRVJBKSB5IHZlbW9zIHF1ZSBlcyB1biByZWdpc3RybyBhaXNsYWRvIHBhcmEgZXNlIGTDrWEsIHBvciBsbyBxdWUgbG8gZWxpbWluYW1vcy4NCmBgYHtyfQ0Kc3VtbWFyeShtZXRfbGltcGlvKSAjIEFob3JhIGxhIG1pbmltYSBlcyBkZSAtMzkuOSB5IGxhIG1heGltYSBkZSA0NS42MA0KDQojIFBlcm8gbm8gcXVlcmVtb3MgcXVlIGhheWEgTkEuIMK/Q8OzbW8gcG9kZW1vcyBhcHJveGltYXIgYSBsYSB0ZW1wZXJhdHVyYSBkZSBlc2UgZMOtYT8gSGFjZW1vcyBlbCBwcm9tZWRpbyBkZSBkb3MgaG9yYXMgYXRyYXMgeSBkb3MgaG9yYXMgYWRlbGFudGUuDQoNCm1ldF9saW1waW9fMS4yIDwtIG1ldF9saW1waW8gfD4NCiAgYXJyYW5nZShOT01CUkUsIEZFQ0hBKSB8PiAjTWUgZG95IGN1ZW50YSBxdWUgZWwgTk9NQlJFIGVuIGxhcyBwcmltZXJhcyAxMjAgZmlsYXMgc29uIHVuIG7Dum1lcm8gKDI4KSBvIHVuYSBmZWNoYSwgcXVlIG5pIHNpcXVpZXJhIGNvcnJlc3BvbmRlIGEgbGEgZmVjaGEgcmVhbC4gUXVpemFzIHBvZHJpYW1vcyBjb21wYXJhciBjb24gZWwgZGlhIGFudGVyaW9yIGEgdmVyIHF1ZSBlc3RhY2lvbmVzIGhheSBlbiB1bmEgeSBubyBlbiBsYSBvdHJhIHBlcm8sIGFsIHNlciB0YW4gcG9jYXMsIHByZWZpZXJvIGJvcnJhcmxhcy4NCiAgc2xpY2UoLSgxOjEyMCkpDQoNCm5hX3RlbXAgPC0gbWV0X2xpbXBpb18xLjIgfD4NCiAgZmlsdGVyKGlzLm5hKFRFTVApKSB8Pg0KICBncm91cF9ieShGRUNIQSwgTk9NQlJFKSB8Pg0KICBzdW1tYXJpc2UobiA9IG4oKSkgfD4NCiAgYXJyYW5nZShkZXNjKG4pKSANCm5hX3RlbXANCiMgSGF5IG1hcyBkZSB1bm8gcG9yIGTDrWEgcG9yIGVzdGFjaW9uLiBFcyBtw6FzLCBoYXkgdW4gZGlhIGVuIHVuYSBlc3RhY2lvbiBxdWUgdGllbmUgTkEgZW4gMjIgcmVnaXN0cm9zLiBDYW1iaW8gZGUgcGxhbmVzOiBIYWNlbW9zIGRlIGRvcyBkaWFzIGF0cmFzIHkgZG9zIGRpYXMgYWRlbGFudGUgZW4gZXNhIG1pc21hIGhvcmEuIEVuIGxvcyB2YWxvcmVzIHF1ZSB2dWVsdmEgYSBoYWJlciBOQSBoYWNlbW9zIGRvcyBob3JhcyBhdHJhcyB5IGRvcyBhZGVsYW50ZQ0KDQojIC0tLSBQcmVwYXJhY2nDs24gLS0tDQoNCm1ldF9pdGVyYXRpdm8gPC0gbWV0X2xpbXBpb18xLjINCg0KIyBPYnRlbmVtb3MgZWwgY29udGVvIGluaWNpYWwgZGUgTkFzDQpwcmV2aW91c19uYV9jb3VudCA8LSBzdW0oaXMubmEobWV0X2l0ZXJhdGl2byRURU1QKSkNCg0KY2F0KCJDb250ZW8gaW5pY2lhbCBkZSBOQXM6IiwgcHJldmlvdXNfbmFfY291bnQsICJcbiIpDQoNCndoaWxlIChUUlVFKSB7DQogIG1ldF9pdGVyYXRpdm8gPC0gbWV0X2l0ZXJhdGl2byB8Pg0KICAgIGdyb3VwX2J5KE5PTUJSRSwgSE9SQSkgfD4NCiAgICBhcnJhbmdlKEZFQ0hBLCAuYnlfZ3JvdXAgPSBUUlVFKSB8Pg0KICAgIG11dGF0ZSgNCiAgICB0ZW1wX3Byb21lZGlvX21vdmlsID0gc2xpZGVfZGJsKA0KICAgICAgVEVNUCwgDQogICAgICB+bWVhbigueCwgbmEucm0gPSBUUlVFKSwgDQogICAgICAuYmVmb3JlID0gMiwgDQogICAgICAuYWZ0ZXIgPSAyDQogICAgKSwNCiAgICBURU1QID0gaWZfZWxzZShpcy5uYShURU1QKSwgdGVtcF9wcm9tZWRpb19tb3ZpbCwgVEVNUCkNCiAgKSB8Pg0KICB1bmdyb3VwKCkgfD4NCiAgICBzZWxlY3QoLXRlbXBfcHJvbWVkaW9fbW92aWwpIA0KDQogICMgNC4gQ2FsY3VsYW1vcyBlbCBudWV2byBjb250ZW8gZGUgTkFzDQogIG5ld19uYV9jb3VudCA8LSBzdW0oaXMubmEobWV0X2l0ZXJhdGl2byRURU1QKSkNCiAgDQogIGNhdCgiSXRlcmFjacOzbiBjb21wbGV0YWRhLiBOQXMgcmVzdGFudGVzOiIsIG5ld19uYV9jb3VudCwgIlxuIikNCg0KICAjIDUuIENvbmRpY2nDs24gZGUgc2FsaWRhDQogIGlmIChuZXdfbmFfY291bnQgPCBwcmV2aW91c19uYV9jb3VudCkgew0KICAgIHByZXZpb3VzX25hX2NvdW50IDwtIG5ld19uYV9jb3VudA0KICB9IGVsc2Ugew0KICAgIGNhdCgiRXN0YWJpbGl6YWRvLiBObyBodWJvIG3DoXMgcmVkdWNjacOzbiBkZSBOQXMuIFNhbGllbmRvIGRlbCBidWNsZS5cbiIpDQogICAgYnJlYWsgIyDCoVNhbGltb3MgZGVsIHdoaWxlIQ0KICB9DQp9DQoNCnN1bW1hcnkobWV0X2l0ZXJhdGl2bykgIyAyOSBOQXMgcmVzdGFudGVzDQoNCiMgUHJvYmFtb3MgY29uIG90cmEgbMOzZ2ljYS4gUHJvbWVkaW8gZGUgdW5hIGhvcmEgYW50ZXMgeSB1bmEgZGVzcHXDqXMNCg0KcHJldmlvdXNfbmFfY291bnQgPC0gc3VtKGlzLm5hKG1ldF9pdGVyYXRpdm8kVEVNUCkpDQoNCmNhdCgiQ29udGVvIGluaWNpYWwgZGUgTkFzOiIsIHByZXZpb3VzX25hX2NvdW50LCAiXG4iKQ0KDQptZXRfaXRlcmF0aXZvIDwtIG1ldF9pdGVyYXRpdm8gfD4NCiAgICBncm91cF9ieShOT01CUkUsIEZFQ0hBKSB8Pg0KICAgIGFycmFuZ2UoSE9SQSwgLmJ5X2dyb3VwID0gVFJVRSkgfD4NCiAgICBtdXRhdGUoDQogICAgdGVtcF9wcm9tZWRpb19tb3ZpbCA9IHNsaWRlX2RibCgNCiAgICAgIFRFTVAsIA0KICAgICAgfm1lYW4oLngsIG5hLnJtID0gVFJVRSksIA0KICAgICAgLmJlZm9yZSA9IDIsIA0KICAgICAgLmFmdGVyID0gMg0KICAgICksDQogICAgVEVNUCA9IGlmX2Vsc2UoaXMubmEoVEVNUCksIHRlbXBfcHJvbWVkaW9fbW92aWwsIFRFTVApDQogICkgfD4NCiAgdW5ncm91cCgpIHw+DQogICAgc2VsZWN0KC10ZW1wX3Byb21lZGlvX21vdmlsKSANCiAgDQpuZXdfbmFfY291bnQgPC0gc3VtKGlzLm5hKG1ldF9pdGVyYXRpdm8kVEVNUCkpDQogIA0KY2F0KCJJdGVyYWNpw7NuIGNvbXBsZXRhZGEuIE5BcyByZXN0YW50ZXM6IiwgbmV3X25hX2NvdW50LCAiXG4iKQ0KIyBRdWVkYSB1biBzb2xvIE5BLCBMbyB2aXN1YWxpemFtb3MNCg0KbWV0X2l0ZXJhdGl2byB8PiANCiAgYXJyYW5nZShkZXNjKGlzLm5hKFRFTVApKSwgRkVDSEEpDQojIEVzIGVuIE9CRVJBLCAyMDIyLTA5LTE4DQoNCm1ldF9pdGVyYXRpdm8gfD4NCiAgZmlsdGVyKE5PTUJSRSA9PSAiT0JFUkEiLA0KICAgICAgICAgRkVDSEEgPT0gIjIwMjItMDktMTgiKQ0KIyBTb2xvIGhheSB1biByZWdpc3RybyBkZSBPYmVyYSBlc2UgZGlhLiBFTiBsYXMgb3RyYXMgY29sdW1uYXMgdGFtYmllbiB0aWVuZSBOQS4gTG8gZWxpbWluYW1vcy4NCg0KbWV0X2xpbXBpb18xLjQgPC0gbWV0X2l0ZXJhdGl2byB8Pg0KICBhcnJhbmdlKGRlc2MoaXMubmEoVEVNUCkpLCBGRUNIQSkgfD4NCiAgc2xpY2UoLTEpDQpzdW1tYXJ5KG1ldF9saW1waW9fMS40KQ0KYGBgDQoNClRyYXMgbGltcGlhciBsYSB0ZW1wZXJhdHVyYSwgZ3VhcmRhbW9zIGVsIHByb2dyZXNvLg0KYGBge3J9DQpzYXZlUkRTKG1ldF9saW1waW9fMS40LCBmaWxlID0gIm1ldF9saW1waW9fMS40LlJkcyIpDQpgYGANCg0KIyMgMi4yLiBMaW1waWV6YSBkZSBIdW1lZGFkIChIVU0pDQoNCkNvcnJlZ2ltb3MgZXhpdG9zYW1lbnRlIHRvZG9zIGxvcyBOQSBkZSB0ZW1wZXJhdHVyYS4gQWhvcmEgYW5hbGl6YW1vcyBsYSBodW1lZGFkLlVuIGhpc3RvZ3JhbWEgbXVlc3RyYSB2YWxvcmVzIGZ1ZXJhIGRlbCByYW5nbyBmw61zaWNvICgwLTEwMCUpLiBMb3MgZmlsdHJhbW9zLCBjb252aXJ0acOpbmRvbG9zIGVuIE5BLg0KDQpgYGB7cn0NCmdncGxvdChtZXRfbGltcGlvXzEuNCwgYWVzKHggPSBIVU0pKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC41KQ0KIyBDb21vIGVzIGRlIGVzcGVyYXIsIGxhIG1heW9yaWEgZGUgdmFsb3JlcyBlc3RhbiBlbnRyZSBlbCAwIHkgZWwgMTAwLiBDb21vIG5vIHB1ZWRlIGhhYmVyIG1hcyBkZSAxMDAlIGQgZWh1bWVkYWQgbG9zIG90cm9zIHNvbiBOQS4NCg0KbWV0X2xpbXBpb18yLjEgPC0gbWV0X2xpbXBpb18xLjQgfD4NCiAgbXV0YXRlKA0KICAgIEhVTSA9IGNhc2Vfd2hlbigNCiAgICAgIEhVTSA+PSAwICYgSFVNIDw9IDEwMCB+IEhVTSAjU2kgZXN0YSBlbnRyZSBlc29zIHZhbG9yZXMgZXMgbGEgbWlzbWEgdGVtcGVyYXR1cmEsIHNpbm8gcG9uZSBOQQ0KICAgICkNCiAgKQ0Kc3VtbWFyeShtZXRfbGltcGlvXzIuMSkNCmBgYA0KDQpFc3RpbWFyZW1vcyBsb3MgdmFsb3JlcyBkZSBodW1lZGFkIHF1ZSBubyB0ZW5lbW9zIGNvbiB1bmEgaG9yYSBhbnRlcyB5IHVuYSBob3JhIGRlc3B1w6lzLCB5YSBxdWUgZXMgbcOhcyB2b2zDoXRpbCBxdWUgbGEgdGVtcGVyYXR1cmEuDQoNCkltcGxlbWVudGFtb3MgdW4gYnVjbGUgd2hpbGUgcXVlIGFwbGljYSB1biBwcm9tZWRpbyBtw7N2aWwgZGUgMyBob3JhcyAoMSBhbnRlcywgMSBkZXNwdcOpcykgYWdydXBhZG8gcG9yIGVzdGFjacOzbiB5IGTDrWEsIHJlcGl0aWVuZG8gaGFzdGEgcXVlIGVsIG7Dum1lcm8gZGUgTkEgc2UgZXN0YWJpbGljZS4NCmBgYHtyfQ0KbWV0X2xpbXBpb18yLjIgPC0gbWV0X2xpbXBpb18yLjENCnByZXZpb3VzX25hX2NvdW50IDwtIHN1bShpcy5uYShtZXRfbGltcGlvXzIuMiRIVU0pKQ0KDQpjYXQoIkNvbnRlbyBpbmljaWFsIGRlIE5BczoiLCBwcmV2aW91c19uYV9jb3VudCwgIlxuIikNCg0Kd2hpbGUgKFRSVUUpIHsNCiAgbWV0X2xpbXBpb18yLjIgPC0gbWV0X2xpbXBpb18yLjIgfD4NCiAgICBncm91cF9ieShOT01CUkUsIEZFQ0hBKSB8Pg0KICAgIGFycmFuZ2UoSE9SQSwgLmJ5X2dyb3VwID0gVFJVRSkgfD4NCiAgICBtdXRhdGUoDQogICAgaHVtX3Byb21lZGlvX21vdmlsID0gc2xpZGVfZGJsKA0KICAgICAgSFVNLCANCiAgICAgIH5tZWFuKC54LCBuYS5ybSA9IFRSVUUpLCANCiAgICAgIC5iZWZvcmUgPSAxLCANCiAgICAgIC5hZnRlciA9IDENCiAgICApLA0KICAgIEhVTSA9IGlmX2Vsc2UoaXMubmEoSFVNKSwgaHVtX3Byb21lZGlvX21vdmlsLCBIVU0pDQogICkgfD4NCiAgdW5ncm91cCgpIHw+DQogICAgc2VsZWN0KC1odW1fcHJvbWVkaW9fbW92aWwpIA0KICANCiAgbmV3X25hX2NvdW50IDwtIHN1bShpcy5uYShtZXRfbGltcGlvXzIuMiRIVU0pKQ0KICANCiAgY2F0KCJJdGVyYWNpw7NuIGNvbXBsZXRhZGEuIE5BcyByZXN0YW50ZXM6IiwgbmV3X25hX2NvdW50LCAiXG4iKQ0KDQogIGlmIChuZXdfbmFfY291bnQgPCBwcmV2aW91c19uYV9jb3VudCkgew0KICAgIHByZXZpb3VzX25hX2NvdW50IDwtIG5ld19uYV9jb3VudA0KICB9IGVsc2Ugew0KICAgIGNhdCgiRXN0YWJpbGl6YWRvLiBObyBodWJvIG3DoXMgcmVkdWNjacOzbiBkZSBOQXMuIFNhbGllbmRvIGRlbCBidWNsZS5cbiIpDQogICAgYnJlYWsgIyDCoVNhbGltb3MgZGVsIHdoaWxlIQ0KICB9DQp9DQoNCnN1bW1hcnkobWV0X2xpbXBpb18yLjIpDQpnZ3Bsb3QobWV0X2xpbXBpb18yLjEsIGFlcyh4ID0gSFVNKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSkNCmdncGxvdChtZXRfbGltcGlvXzIuMiwgYWVzKHggPSBIVU0pKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC41KQ0KYGBgDQoNCkd1YXJkYW1vcyBlbCBwcm9ncmVzbyBudWV2YW1lbnRlLg0KYGBge3J9DQpzYXZlUkRTKG1ldF9saW1waW9fMi4yLCBmaWxlID0gIm1ldF9saW1waW9fMi4yLlJkcyIpDQptZXRfbGltcGlvXzIuMiA8LSByZWFkUkRTKCJtZXRfbGltcGlvXzIuMi5SZHMiKQ0KYGBgDQoNCiMjIDIuMy4gTGltcGllemEgZGUgUHJlc2nDs24gKFBOTSkNCg0KVmVtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBQcmVzacOzbiwgcXVlIGVzdMOhIG1lZGlkYSBlbiBoUGEuDQoNCkVsIGhpc3RvZ3JhbWEgZGUgUHJlc2nDs24gKFBOTSkgZXMgbcOhcyBjb21wbGVqby4gTXVlc3RyYSBsYSBtYXlvcsOtYSBkZSBsb3MgdmFsb3JlcyBlbiBlbCByYW5nbyBsw7NnaWNvICg5MDAtMTA1MCBoUGEpLCBwZXJvIHRhbWJpw6luIGPDum11bG9zIGV4dHJhw7FvcyBjZXJjYSBkZSAxNTAwIHkgMzAwMCBoUGEuDQoNCkFsIGludmVzdGlnYXIgZXN0b3MgdmFsb3JlcywgZGVzY3Vicmltb3MgcXVlIHNlIGNvbmNlbnRyYW4gZW4gZXN0YWNpb25lcyBlc3BlY8OtZmljYXMgKGVqLiAiU0FMVEEgQUVSTyIpLiBVbmEgc2VyaWUgdGVtcG9yYWwgZGUgU2FsdGEgcmV2ZWxhIHF1ZSB0b2RvcyBzdXMgZGF0b3MgZGUgcHJlc2nDs24gc29uIGFuw7NtYWxvcywgc3VnaXJpZW5kbyB1biBzZW5zb3IgZGVmZWN0dW9zby4NCg0KRGVjaWRpbW9zIGZpbHRyYXIgdG9kb3MgbG9zIHZhbG9yZXMgZnVlcmEgZGVsIHJhbmdvIHJlYWxpc3RhIDkwMC0xMDUwIGhQYSwgY29udmlydGnDqW5kb2xvcyBlbiBOQS4NCmBgYHtyfQ0KbWV0X2xpbXBpb18zIDwtIG1ldF9saW1waW9fMi4yDQoNCm1ldF9saW1waW9fMyB8Pg0KICBmaWx0ZXIoTk9NQlJFID09ICJMQSBRVUlBQ0EgT0JTRVJWQVRPUklPIikgfD4NCiAgdmlldygpDQogIA0KDQpnZ3Bsb3QobWV0X2xpbXBpb18zLCBhZXMoeCA9IFBOTSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjUpDQojIEhheSB1bmEgZXNwZWNpZSBkZSBtYWNoYSBhbHJlZGVkb3IgZGUgbG9zIDE1MDBoUGEgeSBwb2NvIG3DoXMgZGUgMzAwMC4gQW1wbGlhbW9zDQpnZ3Bsb3QobWV0X2xpbXBpb18zLCBhZXMoeCA9IFBOTSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjUpICsgDQogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygxMzc1LDE2MjUpKQ0KZ2dwbG90KG1ldF9saW1waW9fMywgYWVzKHggPSBQTk0pKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC41KSArIA0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoMzAwMCwzMjAwKSkNCiMgwr9Qb3IgcXXDqSBzZXLDoT8NCg0KdmFsb3Jlc19yYXJvcyA8LSBtZXRfbGltcGlvXzMgfD4NCiAgZmlsdGVyKFBOTSA+IDExMDAgfCBQTk0gPCA4NTApIHw+DQogIGFycmFuZ2UoUE5NKQ0KDQp2YWxvcmVzX3Jhcm9zX2NvdW50IDwtIHZhbG9yZXNfcmFyb3MgfD4gDQogIGNvdW50KE5PTUJSRSkgfD4gDQogIGFycmFuZ2UoZGVzYyhuKSkNCiMgU2FsdGEgZXMgbGEgZXN0YWNpb24gY29uIG1hcyB2YWxvcmVzIHJhcm9zDQoNCnByZXNpb25fc2FsdGFfZGlhcmlhIDwtIG1ldF9saW1waW9fMyB8Pg0KICBmaWx0ZXIoTk9NQlJFID09ICJTQUxUQSBBRVJPIikgfD4NCiAgZ3JvdXBfYnkoRkVDSEEpIHw+DQogIHN1bW1hcmlzZSgNCiAgICBQUkVTSU9OX21lZGlhID0gbWVhbihQTk0sIG5hLnJtID0gVFJVRSkNCiAgKSB8Pg0KICB1bmdyb3VwKCkNCg0KIyBHcmFmaWNhciBsYSBzZXJpZSBkZSB0aWVtcG8NCmdncGxvdChwcmVzaW9uX3NhbHRhX2RpYXJpYSwgYWVzKHggPSBGRUNIQSwgeSA9IFBSRVNJT05fbWVkaWEpKSArDQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9wb2ludCgpICsgIyBPcGNpb25hbDogbW9zdHJhciBsb3MgcHVudG9zIGRlIGNhZGEgZMOtYQ0KICBsYWJzKA0KICAgIHRpdGxlID0gIkV2b2x1Y2nDs24gZGUgbGEgUHJlc2nDs24gUHJvbWVkaW8gZW4gQmFyaWxvY2hlIiwNCiAgICB5ID0gIlByZXNpw7NuIE1lZGlhIERpYXJpYSAoaFBhKSIsDQogICAgeCA9ICJGZWNoYSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIFBvZGVtb3MgdmVyIHF1ZSBubyBoYXkgdW4gc29sbyBkYXRvIGVuIFNhbHRhIHF1ZSBlc3TDqSBlbiBsbyBwYXLDoW1ldHJvcyBub3JtYWxlcy4gTXV5IHByb2JhYmxlbWVudGUgZWwgc2Vuc29yIGVzdMOpIHJvdG8uDQoNCm1ldF9saW1waW9fMy4xIDwtIG1ldF9saW1waW9fMyB8Pg0KICBtdXRhdGUoDQogICAgUE5NID0gaWZfZWxzZSgNCiAgICAgIFBOTSA+IDkwMCAmIFBOTSA8IDEwNTAsIFBOTSwgTkFfcmVhbF8pDQogICAgKQ0KDQpgYGANCg0KQ29kaWdvIGJvcnJhZG8sIHBlcm8gZW4gcmVzdW1lbjogU2UgaXRlcm8gdGFudGFzIHZlY2VzIHF1ZSBodWJvIHNlc2dvIGRlIHByb3BhZ2FjaW9uLiBMYSBtYXhpbWEgY2FudGlkYWQgZGUgdmFsb3JlcyBxdWUgc2UgcmVwZXRpYW4gcG9yIGVzdGFjaW9uIGVyYSBkZSA1NjEgeSBwYXPDsyBhIDI2ODc2Lg0KDQpQcm9iYW1vcyBvdHJhIGVzdHJhdGVnaWEuIFVzYW1vcyBlbCBwYXF1ZXRlIHpvbyBwYXJhIHVuYSBpbXB1dGFjacOzbiBtw6FzIGNvbnRyb2xhZGE6DQotIG5hLmFwcHJveDogSW50ZXJwb2xhY2nDs24gbGluZWFsIHBhcmEgcmVsbGVuYXIgaHVlY29zIHBlcXVlw7FvcyAobcOheC4gMiBob3JhcykuDQotIG5hLmxvY2Y6ICLDmmx0aW1hIG9ic2VydmFjacOzbiBsbGV2YWRhIGFkZWxhbnRlIiAoTE9DRikgcGFyYSBodWVjb3MgZGUgaGFzdGEgMiBob3Jhcy4NCi0gbmEubG9jZihmcm9tTGFzdCA9IFRSVUUpOiAiUHLDs3hpbWEgb2JzZXJ2YWNpw7NuIHRyYcOtZGEgaGFjaWEgYXRyw6FzIiAoTk9DQikgcGFyYSBsb3MgaHVlY29zIHJlc3RhbnRlcy4NCg0KRXN0ZSBtw6l0b2RvIHJlbGxlbmEgbG9zIE5BIGRlIGZvcm1hIHJvYnVzdGEgc2luIGRpc3RvcnNpb25hciBzaWduaWZpY2F0aXZhbWVudGUgbGEgZGlzdHJpYnVjacOzbiBvcmlnaW5hbC4NCmBgYHtyfQ0KbWV0X3JlbGxlbm9fZmluYWwgPC0gbWV0X2xpbXBpb18zLjEgfD4NCiAgZ3JvdXBfYnkoTk9NQlJFLCBGRUNIQSkgfD4NCiAgYXJyYW5nZShIT1JBLCAuYnlfZ3JvdXAgPSBUUlVFKSB8Pg0KICBtdXRhdGUoDQogICAgUE5NX3JlbGxlbm9fZmluYWwgPSB6b286Om5hLmFwcHJveChQTk0sIG1heGdhcCA9IDIsIG5hLnJtID0gRkFMU0UpLA0KICAgIFBOTV9yZWxsZW5vX2ZpbmFsID0gem9vOjpuYS5sb2NmKFBOTV9yZWxsZW5vX2ZpbmFsLCBuYS5ybSA9IEZBTFNFLCBtYXhnYXAgPSAyKSwNCiAgICBQTk1fcmVsbGVub19maW5hbCA9IHpvbzo6bmEubG9jZihQTk1fcmVsbGVub19maW5hbCwgZnJvbUxhc3QgPSBUUlVFLCBuYS5ybSA9IEZBTFNFLCBtYXhnYXAgPSAyKQ0KICApIHw+DQogIHVuZ3JvdXAoKQ0KDQpuYXNfZmluYWxlcyA8LSBzdW0oaXMubmEobWV0X3JlbGxlbm9fZmluYWwkUE5NX3JlbGxlbm9fZmluYWwpKQ0KY2F0KCJOQXMgaW5pY2lhbGVzOiIsIHN1bShpcy5uYShtZXRfbGltcGlvXzMuMSRQTk0pKSwgIlxuIikNCmNhdCgiTkFzIGZpbmFsZXMgcmVzdGFudGVzOiIsIG5hc19maW5hbGVzLCAiXG4iKQ0KDQpzdW1tYXJ5KG1ldF9yZWxsZW5vX2ZpbmFsKQ0KZ2dwbG90KG1ldF9yZWxsZW5vX2ZpbmFsLCBhZXMoeCA9IFBOTSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjUpDQpnZ3Bsb3QobWV0X3JlbGxlbm9fZmluYWwsIGFlcyh4ID0gUE5NX3JlbGxlbm9fZmluYWwpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC41KSANCiMgRWwgY2FtYmlvIGVuIGxhIGRpc3RyaWJ1Y2lvbiBlcyBhcGVuYXMgcGVyY2VwdGlibGUuIE1lIHF1ZWRvIGNvbiBlc3RhIGxpbXBpZXphIGRlIE5BDQpgYGANCg0KTm8gYXByb3hpbWFtb3MgbcOhcyBOQXMgcGFyYSBxdWUgbm8gaGF5YSBzZXNnbyBkZSBwcm9wYWdhY2nDs24uIEZvcm1hdGVhbW9zIHkgZ3VhcmRhbW9zLg0KDQpgYGB7cn0NCm1ldF9saW1waW9fMy4yIDwtIG1ldF9yZWxsZW5vX2ZpbmFsIHw+DQogIHNlbGVjdCgtUE5NKSB8Pg0KICByZW5hbWUoUE5NID0gUE5NX3JlbGxlbm9fZmluYWwpDQpzYXZlUkRTKG1ldF9saW1waW9fMy4yLCAibWV0X2xpbXBpb18zLjIuUmRzIikNCm1ldF9saW1waW9fMy4yIDwtIHJlYWRSRFMoIm1ldF9saW1waW9fMy4yLlJkcyIpDQpgYGANCg0KDQojIDMuIEluZ2VuaWVyw61hIGRlIFZhcmlhYmxlczogVmllbnRvIChERCB5IEZGKQ0KDQpBaG9yYSBhbmFsaXphbW9zIGxhcyB2YXJpYWJsZXMgZGUgdmllbnRvOiBEaXJlY2Npw7NuIChERCkgeSBWZWxvY2lkYWQgKEZGKS4NCkVsIGhpc3RvZ3JhbWEgZGUgRGlyZWNjacOzbiAoREQpIG11ZXN0cmEgcGljb3MgYW7Ds21hbG9zIGVuIDAgeSA5OTAuDQotIEVuY29udHJhbW9zIHF1ZSBERD0wIGNhc2kgc2llbXByZSBjb3JyZXNwb25kZSBhIEZGPTAgKENhbG1hKS4NCi0gREQ9OTkwIGVzIHVuIHZhbG9yIGNlbnRpbmVsYSBjb25vY2lkbyBxdWUgc2lnbmlmaWNhICJWYXJpYWJsZSIuDQoNClVzYW1vcyBlc3RvIHBhcmEgZG9zIHByb3DDs3NpdG9zOg0KLSBDcmVhciB1bmEgbnVldmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgRERfY2F0ZWdvcmlhIChOb3J0ZSwgTm9yZXN0ZSwgQ2FsbW8sIFZhcmlhYmxlKS4NCi0gTGltcGlhciBsYSBjb2x1bW5hIG51bcOpcmljYSBERDogc2kgRkY9MCwgcG9uZW1vcyBOQTsgc2kgREQ9OTkwLCBwb25lbW9zIE5BLg0KYGBge3J9DQptZXRfbGltcGlvXzQgPC0gbWV0X2xpbXBpb18zLjINCnN1bW1hcnkobWV0X2xpbXBpb180KQ0KDQpnZ3Bsb3QobWV0X2xpbXBpb180LCBhZXMoeCA9IEREKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSkNCg0KRERfY29tbW9uIDwtIG1ldF9saW1waW9fNCB8Pg0KICBncm91cF9ieShERCkgfD4NCiAgc3VtbWFyaXNlKEREID0gbWVhbihERCksIG4gPSBuKCkpIHw+DQogIGFycmFuZ2UoZGVzYyhERCkpDQoNCiMgUHJpbWVybzogwr9RdcOpIGhhY2Vtb3MgY29uIGdyYWRvID0gMD8gwr9FcyBsbyBtaXNtbyBxdWUgMzYwPw0KDQptZXRfbGltcGlvXzQgfD4gDQogIGZpbHRlcihERCA9PSAwKSB8Pg0KICBjb3VudChGRikgIyBEZXNjdWJyaW1vcyBxdWUgc2kgZWwgZ3JhZG8gPSAwIGVuIGNhc2kgdG9kb3MgbG9zIGNhc29zIGxhIHZlbG9jaWRhZCB0YW1iaWVuIGVzIDAuIERlY2lkaW1vcyBwb25lciBOQSBlbiBsb3MgY2Fzb3MgZG9uZGUgRkYgPSAwIHBvcnF1ZSBlbiByZWFsaWRhZCBudW5jYSBodWJvIGRpcmVjY2lvbi4gRGVqYXJsbyBlbiAwIHJvbXBlcmEgbGFzIGVzdGFkaXN0aWNhcyB5IGxvcyBncmFmaWNvcy4gRWwgb3RybyBncmFuIHBpY28gYW5vbWFsbyBlc3RhIGVuIEREID0gOTkwLiBFc3RvIGVzIHVuIHZhbG9yIHNlbnRpbmVsYSBlbiBtZXRlcmVvbG9nw61hLiBQdWVkZSBzaWduaWZpY2FyIE5BLCBjYWxtbyBvIHZhcmlhYmxlLg0KDQptZXRfbGltcGlvXzQuMSA8LSBtZXRfbGltcGlvXzQgfD4NCiAgbXV0YXRlKA0KICAgICAgRERfY2F0ZWdvcmlhID0gY2FzZV93aGVuKA0KICAgICAgRkYgPT0gMCB+ICJDYWxtbyIsDQogICAgICBERCA9PSA5OTAgfiAiVmFyaWFibGUiLA0KICAgICAgaXMubmEoRkYpIH4gTkFfY2hhcmFjdGVyXywNCiAgICAgIChERCA+IDMzNy41IHwgREQgPD0gMjIuNSkgJiBGRiA+IDAgfiAiTm9ydGUiLA0KICAgICAgREQgPiAyMi41ICAmIEREIDw9IDY3LjUgIH4gIk5vcmVzdGUiLA0KICAgICAgREQgPiA2Ny41ICAmIEREIDw9IDExMi41IH4gIkVzdGUiLA0KICAgICAgREQgPiAxMTIuNSAmIEREIDw9IDE1Ny41IH4gIlN1cmVzdGUiLA0KICAgICAgREQgPiAxNTcuNSAmIEREIDw9IDIwMi41IH4gIlN1ciIsDQogICAgICBERCA+IDIwMi41ICYgREQgPD0gMjQ3LjUgfiAiU3Vyb2VzdGUiLA0KICAgICAgREQgPiAyNDcuNSAmIEREIDw9IDI5Mi41IH4gIk9lc3RlIiwNCiAgICAgIEREID4gMjkyLjUgJiBERCA8PSAzMzcuNSB+ICJOb3JvZXN0ZSIsDQogICAgICAuZGVmYXVsdCA9IE5BX2NoYXJhY3Rlcl8NCiAgICApLA0KICAgIEREID0gY2FzZV93aGVuKA0KICAgICAgRkYgPT0gMCB+IE5BX3JlYWxfLA0KICAgICAgREQgPT0gMCAmIGlzLm5hKEZGKSB+IE5BX3JlYWxfLA0KICAgICAgREQgPT0gMCAmIEZGID4gMCB+IDM2MCwNCiAgICAgIEREID4gMzYwIH4gTkFfcmVhbF8sDQogICAgICAuZGVmYXVsdCA9IEREDQogICAgKSwNCiAgICBERF9jYXRlZ29yaWEgPSBmYWN0b3IoRERfY2F0ZWdvcmlhKQ0KICApDQpzdW1tYXJ5KG1ldF9saW1waW9fNC4xKSANCmBgYA0KDQpVbiBoaXN0b2dyYW1hIGRlIFZlbG9jaWRhZCBkZWwgVmllbnRvIChGRikgbXVlc3RyYSBhbGd1bm9zIHZhbG9yZXMgbXV5IGFsdG9zLiBBbCBpbnZlc3RpZ2FybG9zLCB2ZW1vcyBxdWUgcHJvdmllbmVuIGRlIGJhc2VzIGFudMOhcnRpY2FzIHkgc29uLCBkZSBoZWNobywgdmFsb3JlcyB2w6FsaWRvcy4NCmBgYHtyfQ0KDQpnZ3Bsb3QobWV0X2xpbXBpb180LjEpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBGRikpDQoNCm1ldF9saW1waW9fNC4xIHw+DQogIGZpbHRlcihGRiA+IDEyMCkgfD4NCiAgZ3JvdXBfYnkoTk9NQlJFKSB8Pg0KICBzdW1tYXJpemUobj1uKCkpIHw+DQogIGFycmFuZ2UoZGVzYyhuKSkNCiMgTm8gaGF5IHF1ZSBmaWx0cmFyIG5hZGEuIExvcyB2aWVudG9zIGZ1ZXJ0ZXMgc29uIGVuIGJhc2VzIGFudGFydGljYXMNCmBgYA0KDQpFbCBwcsOzeGltbyBwYXNvIGVzIHVuaXIgZWwgZGF0YXNldCBjb24gbGFzIGVzdGFjaW9uZXMuIEhheSBhbGd1bmFzIGVzdGFjaW9uZXMgY3V5b3Mgbm9tYnJlcyBlc3TDoW4gbWFsLg0KDQpEZXRlY3RhbW9zIHF1ZSBtdWNoYXMgZXN0YWNpb25lcyB0aWVuZW4gbm9tYnJlcyBsaWdlcmFtZW50ZSBkaWZlcmVudGVzIChlai4gIkxBIFFVSUFDQSBPQlMuIiB2cyAiTEEgUVVJQUNBIE9CU0VSVkFUT1JJTyIpLiBFc3RhbmRhcml6YW1vcyBlc3RvcyBub21icmVzIHVzYW5kbyBjYXNlX3doZW4gcGFyYSBhc2VndXJhciBxdWUgbGFzIHVuaW9uZXMgZnV0dXJhcyBmdW5jaW9uZW4gY29ycmVjdGFtZW50ZS4NCg0KYGBge3J9DQptZXRfbGltcGlvXzQuMiA8LSBtZXRfbGltcGlvXzQuMSB8Pg0KICBmaWx0ZXIoTk9NQlJFICE9ICJ9IikgfD4NCiAgbXV0YXRlKA0KICAgIE5PTUJSRSA9IGNhc2Vfd2hlbigNCiAgICAgIE5PTUJSRSA9PSAiQlVFTk9TIEFJUkVTIiB+ICJCVUVOT1MgQUlSRVMgT0JTRVJWQVRPUklPIiwNCiAgICAgIE5PTUJSRSA9PSAiRVNDLkFWSUFDSU9OIE1JTElUQVIgQUVSTyIgfiAiRVNDVUVMQSBERSBBVklBQ0lPTiBNSUxJVEFSIEFFUk8iLA0KICAgICAgTk9NQlJFID09ICJFU0NVRUxBIERFIEFWSUFDSU9OIE1JTElUQSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSIEFFUk8iIH4gIkVTQ1VFTEEgREUgQVZJQUNJT04gTUlMSVRBUiBBRVJPIiwNCiAgICAgIE5PTUJSRSA9PSAiTEEgUVVJQUNBIE9CUy4iIH4gIkxBIFFVSUFDQSBPQlNFUlZBVE9SSU8iLA0KICAgICAgTk9NQlJFID09ICJMQVMgRkxPUkVTIEFFUk8iIH4gIkxBUyBGTE9SRVMiLA0KICAgICAgTk9NQlJFID09ICJPQkVSQSBBRVJPIiB+ICJPQkVSQSIsDQogICAgICBOT01CUkUgPT0gIlBDSUEuIFJPUVVFIFNBRU5aIFBFw5FBIEFFUiBPCSIgfiAiUFJFU0lERU5DSUEgUk9RVUUgU0FFTlogUEXDkUEgQUVSTyIsDQogICAgICBOT01CUkUgPT0gIlBJTEFSIE9CUy4iIH4gIlBJTEFSIE9CU0VSVkFUT1JJTyIsDQogICAgICBOT01CUkUgPT0gIlBDSUEuIFJPUVVFIFNBRU5aIFBFw5FBIEFFUiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPIiB+ICJQUkVTSURFTkNJQSBST1FVRSBTQUVOWiBQRcORQSBBRVJPIiwNCiAgICAgIE5PTUJSRSA9PSAiUFJFU0lERU5DSUEgUk9RVUUgU0FFTlogUEUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgw5FBIEFFUk8iIH4gIlBSRVNJREVOQ0lBIFJPUVVFIFNBRU5aIFBFw5FBIEFFUk8iLA0KICAgICAgTk9NQlJFID09ICJTQU4gRkVSTkFORE8iIH4gIlNBTiBGRVJOQU5ETyBBRVJPIiwNCiAgICAgIE5PTUJSRSA9PSAiVkVOQURPIFRVRVJUTyIgfiAiVkVOQURPIFRVRVJUTyBBRVJPIiwNCiAgICAgIE5PTUJSRSA9PSAiVklMTEEgREUgTUFSSUEgREVMIFJJTyBTRUMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTyIgfiAiVklMTEEgREUgTUFSSUEgREVMIFJJTyBTRUNPIiwNCiAgICAgIE5PTUJSRSA9PSAiVklMTEEgTUFSSUEgREVMIFJJTyBTRUNPIiB+ICJWSUxMQSBERSBNQVJJQSBERUwgUklPIFNFQ08iLA0KICAgICAgLmRlZmF1bHQgPSBOT01CUkUgDQogICAgKQ0KICApDQoNCm1ldF9saW1waW9fNC4yIHw+DQogIGdyb3VwX2J5KE5PTUJSRSkgfD4NCiAgc3VtbWFyaXNlKG4gPSBuKCkpIHw+DQogIHZpZXcoKQ0KYGBgDQoNClZvbHZlbW9zIGEgYW5hbGl6YXIgRkYuIFZhbW9zIGEgaGFjZXIgdW5hIGludGVycG9sYWNpb24gcGFyYSBpbXB1dGFyIE5BcywgY29uIHVuIGdhcCBtdXkgY29ydG8gZGUgZG9zIGhvcmFzLiBQcmltZXJvIGxpbXBpYW1vcyB1biBwb2NvIGVsIERGIChWb2x2ZW1vcyBhIGFuYWxpemFyIEZGLiBWYW1vcyBhIGhhY2VyIHVuYSBpbnRlcnBvbGFjaW9uIHBhcmEgaW1wdXRhciBOQXMsIGNvbiB1biBnYXAgbXV5IGNvcnRvIGRlIGRvcyBob3Jhcy4NCg0KUmVhbGl6YW1vcyB1bmEgcGFzYWRhIGZpbmFsIGRlIGxpbXBpZXphOg0KLSBOb3JtYWxpemFtb3MgbG9zIG5vbWJyZXMgZGUgbGFzIGNvbHVtbmFzIChlai4gRkVDSEEgLT4gZmVjaGEpIGNvbiBqYW5pdG9yOjpjbGVhbl9uYW1lcy4NCi0gQ3JlYW1vcyB1bmEgY29sdW1uYSBkYXRldGltZSBmb3JtYWwuDQotIEVsaW1pbmFtb3MgcmVnaXN0cm9zIGR1cGxpY2Fkb3MgKG1pc21hIGVzdGFjacOzbiwgZmVjaGEgeSBob3JhKS4NCi0gUmVhbGl6YW1vcyB1bmEgaW1wdXRhY2nDs24gZmluYWwgeSBjb25zZXJ2YWRvcmEgc29icmUgZmYgKFZlbG9jaWRhZCBkZWwgVmllbnRvKSB1c2FuZG8gem9vOjpuYS5hcHByb3ggeSBuYS5sb2NmIHNvbG8gcGFyYSBodWVjb3MgZGUgMiBob3JhcyBvIG1lbm9zLiBjb2x1bW5hcywgY3JlYW5kbyBkYXRldGltZSB5IGVsaW1pbmFuZG8gZHVwbGljYWRvcykgcGFyYSB5YSB0ZW5lcmxvIGxpc3RvIHBhcmEgbG9zIHByw7N4aW1vcyBhbmFsaXNpcy4NCmBgYHtyfQ0KbWV0X2xpbXBpb180LjMgPC0gbWV0X2xpbXBpb180LjIgfD4NCiAgY2xlYW5fbmFtZXMoKSB8PiAjTm9ybWFsaXphbW9zIGxvcyBub21icmVzIHBhcmEgZWwgc2lndWllbnRlIHBhc28sIHF1ZSB2YSBhIHNlciB1bmlyIGxvcyBkYXRhZnJhbWVzDQogIG11dGF0ZSgNCiAgICBmZWNoYSAgICAgICAgPSBhcy5EYXRlKGZlY2hhKSwNCiAgICBob3JhICAgICAgICAgPSBhcy5pbnRlZ2VyKGhvcmEpLA0KICAgIGRkX2NhdGVnb3JpYSA9IGFzLmZhY3RvcihkZF9jYXRlZ29yaWEpLA0KICAgIHBubSAgICAgICAgICA9IGFzLm51bWVyaWMocG5tKSwNCiAgICAjIEFncmVnYW1vcyBjb2x1bW5hIERhdGV0aW1lDQogICAgZGF0ZXRpbWUgPSBsdWJyaWRhdGU6Om1ha2VfZGF0ZXRpbWUoDQogICAgICB5ZWFyID0gbHVicmlkYXRlOjp5ZWFyKGZlY2hhKSwNCiAgICAgIG1vbnRoID0gbHVicmlkYXRlOjptb250aChmZWNoYSksDQogICAgICBkYXkgPSBsdWJyaWRhdGU6OmRheShmZWNoYSksDQogICAgICBob3VyID0gaG9yYSwNCiAgICAgIHR6ID0gIkFtZXJpY2EvQXJnZW50aW5hL0J1ZW5vc19BaXJlcyINCiAgICApDQogICkgfD4NCiAgIyA9PT09PT0gRHVwbGljYWRvcyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiAgZGlzdGluY3Qobm9tYnJlLCBmZWNoYSwgaG9yYSwgLmtlZXBfYWxsID0gVFJVRSkgJT4lDQoNCiAgIyA9PT09PT09PT09IEludGVycG9sYWNpw7NuIGludHJhLWVzdGFjacOzbiBkZSBmZiA9PT09PT09PT09PT09DQogIA0KICAjIDEuIE9yZGVuYXIgbG9zIGRhdG9zDQogIGFycmFuZ2Uobm9tYnJlLCBkYXRldGltZSkgfD4NCiAgZ3JvdXBfYnkobm9tYnJlKSB8Pg0KICBtdXRhdGUoDQogICAgIyBQYXNvIDE6IEludGVycG9sYWNpw7NuIGxpbmVhbCAoem9vOjpuYS5hcHByb3gpDQogICAgZmZfaW50ZXJwID0gem9vOjpuYS5hcHByb3goZmYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IGFzLm51bWVyaWMoZGF0ZXRpbWUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLnJtID0gRkFMU0UsIG1heGdhcCA9IDIpLA0KICAgIA0KICAgICMgUGFzbyAyOiBSZWxsZW5vIGhhY2lhIGFkZWxhbnRlIChMYXN0IE9ic2VydmF0aW9uIENhcnJpZWQgRm9yd2FyZCkuIFNlIGFwbGljYSBzb2JyZSBsYSBjb2x1bW5hICdmZl9pbnRlcnAnIHJlY2nDqW4gY3JlYWRhIGVuIGVsIHBhc28gYW50ZXJpb3IuDQogICAgZmZfaW50ZXJwID0gem9vOjpuYS5sb2NmKGZmX2ludGVycCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEucm0gPSBGQUxTRSwgbWF4Z2FwID0gMiksDQogICAgIyBQYXNvIDM6IFJlbGxlbm8gaGFjaWEgYXRyw6FzIChOZXh0IE9ic2VydmF0aW9uIENhcnJpZWQgQmFja3dhcmQpLiBTZSBhcGxpY2Egc29icmUgZWwgcmVzdWx0YWRvIGRlbCBQYXNvIDIuDQogICAgZmZfaW50ZXJwID0gem9vOjpuYS5sb2NmKGZmX2ludGVycCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbUxhc3QgPSBUUlVFLCBuYS5ybSA9IEZBTFNFLCBtYXhnYXAgPSAyKQ0KICApIHw+DQogIHVuZ3JvdXAoKSB8Pg0KICBzZWxlY3QoLWZmKSB8Pg0KICByZW5hbWUoZmYgPSBmZl9pbnRlcnApDQoNCm1ldF9kYXRvc19saW1waW8gPC0gbWV0X2xpbXBpb180LjMNCmBgYA0KDQpFc3RlIG1ldF9kYXRvc19saW1waW8gZXMgbnVlc3RybyBjb25qdW50byBkZSBkYXRvcyBob3JhcmlvLCBsaW1waW8geSBwcm9jZXNhZG8uIExvIGd1YXJkYW1vcy4NCg0KYGBge3J9DQpzYXZlUkRTKG1ldF9kYXRvc19saW1waW8sICJtZXRfZGF0b3NfbGltcGlvLlJkcyIpDQptZXRfZGF0b3NfbGltcGlvIDwtIHJlYWRSRFMoIm1ldF9kYXRvc19saW1waW8uUmRzIikNCmBgYA0KDQojIDQuIFVuaWZpY2FjacOzbiBjb24gTWV0YWRhdG9zIHkgRGF0b3MgZGUgUHJlY2lwaXRhY2nDs24NCg0KQWhvcmEgcXVlIG51ZXN0cm9zIGRhdG9zIG9ic2VydmFjaW9uYWxlcyBlc3TDoW4gbGltcGlvcywgbG9zIGVucmlxdWVjZW1vcy4NCi0gQ2FyZ2Ftb3Mgc21uX2VzdGFjaW9uZXMuY3N2IChxdWUgY29udGllbmUgbGF0aXR1ZCwgbG9uZ2l0dWQgeSBwcm92aW5jaWEpIHkgbG8gdW5pbW9zLg0KLSBDYXJnYW1vcyB1biBkYXRhc2V0IHNlcGFyYWRvIGRlIHNtbl9wcmVjaXBpdGFjaW9uZXMtMTk5MS0yMDI0LnR4dC4NClVuaW1vcyBlc3RvcyBkYXRvcy4NCmBgYHtyfQ0Kc21uX2VzdGFjaW9uZXMgPC0gcmVhZF9jc3YoInNtbl9lc3RhY2lvbmVzLmNzdiIpDQpzbW5fZXN0YWNpb25lcyA8LSBzbW5fZXN0YWNpb25lcyB8Pg0KICBjbGVhbl9uYW1lcygpDQptZXRfY29uX2luZm8gPC0gbWV0X2RhdG9zX2xpbXBpbyB8Pg0KICBsZWZ0X2pvaW4oc21uX2VzdGFjaW9uZXMsIGJ5ID0gIm5vbWJyZSIpDQoNCnByb2Nlc2FyX2FyY2hpdm9fcHJlY2lwaXRhY2lvbiA8LSBmdW5jdGlvbihydXRhX2FyY2hpdm8pIHsNCiAgDQogIHByaW50KHBhc3RlKCJQcm9jZXNhbmRvIChtb2RvIHByZWNpcGl0YWNpw7NuKToiLCBydXRhX2FyY2hpdm8pKQ0KICANCiAgdHJ5Q2F0Y2goew0KICAgIA0KICAgICMgLS0tIDEuIExlZXIgZWwgQ1NWIC0tLQ0KICAgIGRhdG9zX3ByZWNpcCA8LSByZWFkcjo6cmVhZF9jc3YoDQogICAgICBydXRhX2FyY2hpdm8sDQogICAgICBsb2NhbGUgPSBsb2NhbGUoZW5jb2RpbmcgPSAibGF0aW4xIiksDQogICAgICBzaG93X2NvbF90eXBlcyA9IEZBTFNFICMgT2N1bHRhIGxvcyBtZW5zYWplcyBkZSB0aXBvIGRlIGNvbHVtbmENCiAgICApDQogICAgDQogICAgIyAtLS0gMi4gT21pdGlyIGFyY2hpdm9zIHZhY8Otb3MgLS0tDQogICAgaWYgKGlzLm51bGwoZGF0b3NfcHJlY2lwKSB8fCBucm93KGRhdG9zX3ByZWNpcCkgPT0gMCkgew0KICAgICAgcmV0dXJuKE5VTEwpDQogICAgfQ0KICAgIA0KICAgICMgLS0tIDMuIExpbXBpYXIgeSByZW5vbWJyYXIgY29sdW1uYXMgLS0tDQogICAgIyBFbCBub21icmUgZGUgdHUgdGVyY2VyYSBjb2x1bW5hICJQcmVjaXBpdGFjaW9uIChtbSkgIiBlcyBwcm9ibGVtw6F0aWNvDQogICAgIyAodGllbmUgdW4gZXNwYWNpbyBvIGNhcmFjdGVyIHJhcm8gYWwgZmluYWwpLg0KICAgICMgTGEgZm9ybWEgbcOhcyByb2J1c3RhIGRlIHJlbm9tYnJhcmxvIGVzIHBvciBzdSBwb3NpY2nDs24uDQogICAgDQogICAgIyBBcXXDrSBzZWxlY2Npb25hbW9zIGxhcyAzIGNvbHVtbmFzIHkgbGVzIGRhbW9zIG5vbWJyZXMgbGltcGlvcw0KICAgIGRhdG9zX2xpbXBpb3MgPC0gZGF0b3NfcHJlY2lwIHw+DQogICAgICBzZWxlY3QoDQogICAgICAgIEVzdGFjaW9uID0gMSwNCiAgICAgICAgRmVjaGEgPSAyLA0KICAgICAgICBQcmVjaXBpdGFjaW9uX21tID0gMw0KICAgICAgKQ0KICAgICAgDQogICAgcmV0dXJuKGRhdG9zX2xpbXBpb3MpDQogICAgDQogIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgew0KICAgIHdhcm5pbmcocGFzdGUoIkVycm9yIEdSQVZFIHByb2Nlc2FuZG8gKG1vZG8gcHJlY2lwaXRhY2nDs24pOiIsIHJ1dGFfYXJjaGl2bywgIi0iLCBlJG1lc3NhZ2UpKQ0KICAgIHJldHVybihOVUxMKQ0KICB9KQ0KfQ0KDQpkYXRvc19kZV9sbHV2aWEgPC0gcHJvY2VzYXJfYXJjaGl2b19wcmVjaXBpdGFjaW9uKCJzbW5fcHJlY2lwaXRhY2lvbmVzLTE5OTEtMjAyNC9zbW5fcHJlY2lwaXRhY2lvbmVzLTE5OTEtMjAyNC50eHQiKQ0KDQpkYXRvc19kZV9sbHV2aWEgPC0gZGF0b3NfZGVfbGx1dmlhIHw+DQogIHJlbmFtZSgNCiAgICBucm8gPSBFc3RhY2lvbiwNCiAgICBmZWNoYSA9IEZlY2hhLA0KICAgIHByZWNpcGl0YWNpb24gPSBQcmVjaXBpdGFjaW9uX21tDQogICkNCg0KZGZfZmluYWwgPC0gbWV0X2Nvbl9pbmZvIHw+DQogIGxlZnRfam9pbigNCiAgICBkYXRvc19kZV9sbHV2aWEsDQogICAgYnkgPSBjKCJucm8iLCAiZmVjaGEiKQ0KICApICANCiAgDQoNCmRmX2xsdXZpYV9jb25faW5mbyA8LSBkYXRvc19kZV9sbHV2aWEgfD4NCiAgbGVmdF9qb2luKHNtbl9lc3RhY2lvbmVzLCBieSA9ICJucm8iKQ0KYGBgDQoNCkd1YXJkYW1vcyBsb3MgZGF0YWZyYW1lcyByZXN1bHRhbnRlczogZGZfZmluYWwgKGRhdG9zIGhvcmFyaW9zIHVuaWRvcyBjb24gbWV0YWRhdGEgeSBwcmVjaXBpdGFjacOzbiBkaWFyaWEpIHkgZGZfbGx1dmlhX2Nvbl9pbmZvIChkYXRvcyBkZSBwcmVjaXBpdGFjacOzbiBkaWFyaW9zIHVuaWRvcyBjb24gbWV0YWRhdGEpLg0KDQpgYGB7cn0NCnNhdmVSRFMoZGZfZmluYWwsICJkZl9maW5hbC5SZHMiKQ0Kc2F2ZVJEUyhkZl9sbHV2aWFfY29uX2luZm8sICJkZl9sbHV2aWFfY29uX2luZm8uUmRzIikNCg0KDQpkZl9maW5hbCA8LSByZWFkUkRTKCJkZl9maW5hbC5SZHMiKQ0KZGZfbGx1dmlhX2Nvbl9pbmZvIDwtIHJlYWRSRFMoImRmX2xsdXZpYV9jb25faW5mby5SZHMiKQ0KYGBgDQoNCiMjIDQuMS4gRGVzYWdyZWdhY2nDs24gVGVtcG9yYWwgZGUgUHJlY2lwaXRhY2nDs24NCg0KSGF5IHVuIHByb2JsZW1hLiBFbCBkZiBkZSBwcmVjaXBpdGFjaW9uZXMgZXN0w6EgcG9yIGTDrWEsIHBlcm8gZWwgZGUgbG9zIGRhdG9zIGVzdMOhIHBvciBob3JhLg0KDQpFc3RvIHByZXNlbnRhIHVuIGRlc2Fmw61vIGNsYXZlOiBudWVzdHJvIGRhdGFzZXQgcHJpbmNpcGFsIGVzIGhvcmFyaW8sIHBlcm8gbGEgcHJlY2lwaXRhY2nDs24gZXMgdW4gdG90YWwgZGlhcmlvLiBQYXJhIGFuYWxpemFybG9zIGp1bnRvcywgZGViZW1vcyBkZXNhZ3JlZ2FyIGVzZSB0b3RhbCBkaWFyaW8gZW4gZXN0aW1hY2lvbmVzIGhvcmFyaWFzLg0KDQpJbXBsZW1lbnRhbW9zIHVuIHNpc3RlbWEgbMOzZ2ljbyBiYXNhZG8gZW4gbGEgaHVtZWRhZDoNCi0gU2kgbGEgcHJlY2lwaXRhY2nDs24gZGlhcmlhIGVzIDAsIHRvZGFzIGxhcyBob3JhcyBzb24gMC4NCi0gUGxhbiBBOiBTaSBoYXkgaG9yYXMgY29uIGh1bWVkYWQgPj0gOTAlLCByZXBhcnRpbW9zIGxhIGxsdXZpYSBlcXVpdGF0aXZhbWVudGUgc29sbyBlbnRyZSBlc2FzIGhvcmFzLg0KLSBQbGFuIEI6IFNpIG5vIGhheSBob3JhcyA+PSA5MCUsIGJ1c2NhbW9zIGhvcmFzIGNvbiBodW1lZGFkID49IDcwJS4gUmVwYXJ0aW1vcyBsYSBsbHV2aWEgZGUgZm9ybWEgcG9uZGVyYWRhIChtw6FzIGh1bWVkYWQsIG3DoXMgbGx1dmlhKSBlbnRyZSBlc2FzIGhvcmFzLg0KLSBQbGFuIEM6IFNpIHRvZGFzIGxhcyBob3JhcyB0aWVuZW4gPCA3MCUgZGUgaHVtZWRhZCwgcmVwYXJ0aW1vcyBsYSBsbHV2aWEgZXF1aXRhdGl2YW1lbnRlIGVudHJlIGxhcyAyNCBob3Jhcy4NCg0KRXN0byBjcmVhIGxhIG51ZXZhIGNvbHVtbmEgcHJlY2lwX2hvcmFyaWEuDQoNCmBgYHtyfQ0KVU1CUkFMX0FMVE8gPC0gOTANClVNQlJBTF9CQUpPIDwtIDcwDQoNCmRmX2ZpbmFsXzEuMiA8LSBkZl9maW5hbCB8Pg0KICBncm91cF9ieShub21icmUsIGZlY2hhKSB8Pg0KICBtdXRhdGUoDQogICAgIyAtLS0gQ29udmVyc2nDs24geSBBeXVkYXMgUGxhbiBBIChVbWJyYWwgOTAlKSAtLS0NCiAgICBwcmVjaXBpdGFjaW9uID0gYXMubnVtZXJpYyhwcmVjaXBpdGFjaW9uKSwNCiAgICBlc19ob3JhX2h1bWVkYV9hbHRhID0gKGh1bSA+PSBVTUJSQUxfQUxUTyAmICFpcy5uYShodW0pKSwNCiAgICBuX2hvcmFzX2h1bWVkYXNfYWx0YSA9IHN1bShlc19ob3JhX2h1bWVkYV9hbHRhKSwNCiAgICANCiAgICAjIC0tLSBBeXVkYXMgUGxhbiBCIChQb25kZXJhZG8pIC0tLQ0KICAgIGh1bV9hX3BvbmRlcmFyID0gaWZfZWxzZShodW0gPj0gVU1CUkFMX0JBSk8gJiAhaXMubmEoaHVtKSwgaHVtLCAwKSwNCiAgICB0b3RhbF9odW1fcG9uZGVyYWRhX2RpYSA9IHN1bShodW1fYV9wb25kZXJhciwgbmEucm0gPSBUUlVFKSwNCg0KICAgICMgPT09IEVsIENhc2UgV2hlbiBGaW5hbCA9PT0NCiAgICBwcmVjaXBfaG9yYXJpYSA9IGNhc2Vfd2hlbigNCiAgICAgIA0KICAgICAgaXMubmEocHJlY2lwaXRhY2lvbikgfiBOQV9yZWFsXywNCiAgICAgIHByZWNpcGl0YWNpb24gPT0gMCB+IDAsDQogICAgICANCiAgICAgIHByZWNpcGl0YWNpb24gPiAwICYgbl9ob3Jhc19odW1lZGFzX2FsdGEgPiAwICYgZXNfaG9yYV9odW1lZGFfYWx0YSA9PSBUUlVFIH4gcHJlY2lwaXRhY2lvbiAvIG5faG9yYXNfaHVtZWRhc19hbHRhLA0KICAgICAgcHJlY2lwaXRhY2lvbiA+IDAgJiBuX2hvcmFzX2h1bWVkYXNfYWx0YSA+IDAgJiBlc19ob3JhX2h1bWVkYV9hbHRhID09IEZBTFNFIH4gMCwNCiAgICAgIA0KICAgICAgcHJlY2lwaXRhY2lvbiA+IDAgJiBuX2hvcmFzX2h1bWVkYXNfYWx0YSA9PSAwICYgdG90YWxfaHVtX3BvbmRlcmFkYV9kaWEgPiAwIH4gKGh1bV9hX3BvbmRlcmFyIC8gdG90YWxfaHVtX3BvbmRlcmFkYV9kaWEpICogcHJlY2lwaXRhY2lvbiwNCiAgICAgIA0KICAgICAgcHJlY2lwaXRhY2lvbiA+IDAgJiBuX2hvcmFzX2h1bWVkYXNfYWx0YSA9PSAwICYgdG90YWxfaHVtX3BvbmRlcmFkYV9kaWEgPT0gMCB+IHByZWNpcGl0YWNpb24gLyBuKCksDQogICAgICANCiAgICAgIC5kZWZhdWx0ID0gTkFfcmVhbF8gDQogICAgKQ0KICApIHw+DQogIHVuZ3JvdXAoKSB8Pg0KICBzZWxlY3QoDQogICAgLWVzX2hvcmFfaHVtZWRhX2FsdGEsIC1uX2hvcmFzX2h1bWVkYXNfYWx0YSwgDQogICAgLWh1bV9hX3BvbmRlcmFyLCAtdG90YWxfaHVtX3BvbmRlcmFkYV9kaWENCiAgKXw+DQogIGFycmFuZ2UoZmVjaGEpDQoNCiMgRGljZSBxdWUgaGF5IDIxODkgYWR2ZXJ0ZW5jaWFzIGNvbiBhcy5udW1lcmljKHByZWNpcGl0YWNpb24pDQoNCmRmX2ZpbmFsIHw+DQogIGZpbHRlcihpcy5uYShhcy5udW1lcmljKHByZWNpcGl0YWNpb24pKSkgfD4NCiAgZGlzdGluY3QocHJlY2lwaXRhY2lvbikgI3NvbiAvTiBvIE5BDQpgYGANCg0KR3VhcmRhbW9zIGVzdGUgZGF0YWZyYW1lIGZpbmFsLCBxdWUgYWhvcmEgZXN0w6EgY29tcGxldG8geSBsaXN0byBwYXJhIGVsIGFuw6FsaXNpcy4NCg0KYGBge3J9DQpzYXZlUkRTKGRmX2ZpbmFsXzEuMiwgImRmX2ZpbmFsXzEuMi5SZHMiKQ0KZGZfZmluYWxfMS4yIDwtIHJlYWRSRFMoImRmX2ZpbmFsXzEuMi5SZHMiKQ0KYGBgDQoNCiMgNS4gQ3JlYWNpw7NuIGRlIERhdGFzZXRzIEFncmVnYWRvcw0KDQpBaG9yYSBjcmVhbW9zIGRhdGFmcmFtZXMgcXVlIG5vcyBheXVkYXLDoW4gbcOhcyBhZGVsYW50ZSBwYXJhIGxvcyBncsOhZmljb3MuDQoNClBhcmEgcmVhbGl6YXIgdW4gYW7DoWxpc2lzIGEgbml2ZWwgbmFjaW9uYWwsIHVuIHNpbXBsZSBwcm9tZWRpbyBkZSB0b2RvcyBsb3MgcmVnaXN0cm9zIGhvcmFyaW9zIHNlcsOtYSBpbmNvcnJlY3RvIChzZXNnYWRvKS4gRGViZW1vcyBjcmVhciB1biBwcm9tZWRpbyAianVzdG8iLg0KLSBkYWlseV9zdGF0aW9uOiBBZ3JlZ2Ftb3MgbG9zIGRhdG9zIGhvcmFyaW9zIHBhcmEgb2J0ZW5lciByZXPDum1lbmVzIGRpYXJpb3MgcG9yIGVzdGFjacOzbiAobWVkaWEgZGUgdGVtcCwgc3VtYSBkZSBwcmVjaXAsIGV0Yy4pLg0KLSBtb250aGx5X2J5X3N0YXRpb25fZmFpcjogQWdyZWdhbW9zIGxvcyByZXPDum1lbmVzIGRpYXJpb3MgYSBtZW5zdWFsZXMsIHBvciBlc3RhY2nDs24uIEZpbHRyYW1vcyBsb3MgbWVzZXMgcXVlIG5vIHRlbmdhbiBhbCBtZW5vcyAyNSBkw61hcyBkZSBkYXRvcyAocGFyYSBxdWUgc2VhbiByZXByZXNlbnRhdGl2b3MpLg0KLSBtb250aGx5X25hdGlvbmFsX2ZhaXI6IENhbGN1bGFtb3MgZWwgcHJvbWVkaW8gbmFjaW9uYWwgImp1c3RvIi4gVG9tYW1vcyBsb3MgcHJvbWVkaW9zIG1lbnN1YWxlcyBkZSBjYWRhIGVzdGFjacOzbiB5IGNhbGN1bGFtb3MgbGEgbWVkaWEgZGUgZXNvcyB2YWxvcmVzLiBBc8OtLCBjYWRhIGVzdGFjacOzbiB0aWVuZSB1biBzb2xvICJ2b3RvIiBwb3IgbWVzLiBGaWx0cmFtb3MgbG9zIG1lc2VzIG5hY2lvbmFsZXMgcXVlIG5vIHRlbmdhbiBkYXRvcyBkZSBhbCBtZW5vcyA2MCBlc3RhY2lvbmVzLg0KDQpgYGB7cn0NCmRmIDwtIGRmX2ZpbmFsXzEuMiANCg0KIyA9PT09PT09PT09PT09PT09PT0gQUdSRUdBRE9TIFBBUkEgRURBID09PT09PT09PT09PT09PT09PT09PT0NCg0KIyAtLS0tIERpYXJpbyBwb3IgZXN0YWNpw7NuIC0tLS0NCmRhaWx5X3N0YXRpb24gPC0gZGYgfD4NCiAgIyBBZ3J1cGFtb3MgcG9yIGxhcyBjb2x1bW5hcyBkZSBsYSBlc3RhY2nDs24geSBwb3IgZMOtYS9hw7FvL21lcw0KICBncm91cF9ieSgNCiAgICBucm8sIG5vbWJyZSwgcHJvdmluY2lhLCBsYXRpdHVkLCBsb25naXR1ZCwgDQogICAgeW1kID0gZmVjaGEsDQogICAgeWVhciA9IHllYXIoZGF0ZXRpbWUpLCAgICANCiAgICBtb250aCA9IG1vbnRoKGRhdGV0aW1lKSAgIA0KICApICU+JQ0KICAjIENhbGN1bGFtb3MgbG9zIHJlc8O6bWVuZXMgZGlhcmlvcw0KICBzdW1tYXJpc2UoDQogICAgcmVnaXN0cm9zICA9IG4oKSwNCiAgICB0ZW1wX21lYW4gID0gaWYgKGFsbChpcy5uYSh0ZW1wKSkpIE5BX3JlYWxfIGVsc2UgbWVhbih0ZW1wLCBuYS5ybSA9IFRSVUUpLA0KICAgIHRlbXBfc2QgICAgPSBzZCh0ZW1wLCBuYS5ybSA9IFRSVUUpLA0KICAgIGh1bV9tZWFuICAgPSBpZiAoYWxsKGlzLm5hKGh1bSkpKSBOQV9yZWFsXyBlbHNlIG1lYW4oaHVtLCBuYS5ybSA9IFRSVUUpLA0KICAgIHBubV9tZWFuICAgPSBpZiAoYWxsKGlzLm5hKHBubSkpKSBOQV9yZWFsXyBlbHNlIG1lYW4ocG5tLCBuYS5ybSA9IFRSVUUpLA0KICAgIHByZWNpcF9zdW0gPSBzdW0ocHJlY2lwX2hvcmFyaWEsIG5hLnJtID0gVFJVRSksDQogICAgLmdyb3VwcyA9ICdkcm9wJyANCiAgKQ0KDQojIC0tLS0gTmFjaW9uYWwgbWVuc3VhbCAiaW5nZW51byIgKHBhcmEgY29tcGFyYXIpIC0tLS0NCm1vbnRobHlfbmF0aW9uYWxfcmF3IDwtIGRmIHw+DQogIGdyb3VwX2J5KA0KICAgIHllYXIgPSB5ZWFyKGRhdGV0aW1lKSwNCiAgICBtb250aCA9IG1vbnRoKGRhdGV0aW1lKQ0KICApIHw+DQogIHN1bW1hcmlzZSgNCiAgICB0ZW1wX21lYW4gPSBtZWFuKHRlbXAsIG5hLnJtID0gVFJVRSksDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApIHw+DQogIG11dGF0ZSgNCiAgICB5bSA9IGFzLkRhdGUocGFzdGUwKHllYXIsICItIiwgc3ByaW50ZigiJTAyZCIsIG1vbnRoKSwgIi0wMSIpKQ0KICApDQoNCiMgLS0tLSBOYWNpb25hbCBtZW5zdWFsICJqdXN0byIgKGZhaXIpOiBkaWFyaW8tPm1lbnN1YWwgcG9yIGVzdGFjacOzbi0+cHJvbWVkaW8gbmFjaW9uYWwgLS0tLQ0KDQptb250aGx5X2J5X3N0YXRpb25fZmFpciA8LSBkYWlseV9zdGF0aW9uIHw+DQogIGdyb3VwX2J5KA0KICAgIG5ybywgDQogICAgeW0gPSBmbG9vcl9kYXRlKHltZCwgIm1vbnRoIikgIyByZWRvbmRlYSBsYSBmZWNoYSBhbCBwcmltZXIgZMOtYSBkZWwgbWVzDQogICkgfD4NCiAgc3VtbWFyaXNlKA0KICAgIHkgPSBtZWFuKHRlbXBfbWVhbiwgbmEucm0gPSBUUlVFKSwNCiAgICBuZGF5cyA9IG4oKSwNCiAgICAuZ3JvdXBzID0gJ2Ryb3AnDQogICkgfD4NCiAgZmlsdGVyKG5kYXlzID49IDI1KQ0KDQptb250aGx5X25hdGlvbmFsX2ZhaXIgPC0gbW9udGhseV9ieV9zdGF0aW9uX2ZhaXIgfD4NCiAgZ3JvdXBfYnkoeW0pIHw+DQogICMgQ2FsY3VsYW1vcyBlbCBwcm9tZWRpbyBkZSBsb3MgcHJvbWVkaW9zIGRlIGxhcyBlc3RhY2lvbmVzICh5KSB5IGNvbnRhbW9zIGN1w6FudGFzIGVzdGFjaW9uZXMgKG5fZXN0KSBwYXNhcm9uIGVsIGZpbHRybyBhbnRlcmlvcg0KICBzdW1tYXJpc2UoDQogICAgdGVtcF9tZWFuID0gbWVhbih5LCBuYS5ybSA9IFRSVUUpLA0KICAgIG5fZXN0ID0gbigpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKSB8Pg0KICBmaWx0ZXIobl9lc3QgPj0gNjApIHw+DQogIGFycmFuZ2UoeW0pDQoNCg0KIyAtLS0gSW1wcmVzacOzbiBkZWwgcmVzdW1lbiBkZSBjb2JlcnR1cmEgLS0tDQpjYXQoIkNvYmVydHVyYSBtZWRpYSBkZSBlc3RhY2lvbmVzIHBvciBtZXMgKGZhaXIpOlxuIikNCm1vbnRobHlfbmF0aW9uYWxfZmFpciB8Pg0KICBzdW1tYXJpc2UoDQogICAgbWVhbl9uX2VzdCA9IG1lYW4obl9lc3QpLA0KICAgIG1pbl9uX2VzdCA9IG1pbihuX2VzdCksDQogICAgbWF4X25fZXN0ID0gbWF4KG5fZXN0KQ0KICApICU+JQ0KICBwcmludCgpDQpgYGANCg0KQWhvcmEgY29tcGFyYW1vcyBlbCBtw6l0b2RvICJyYXciIGNvbiBlbCAiZmFpciIuDQoNCkVsIGdyw6FmaWNvIHJlc3VsdGFudGUgbXVlc3RyYSBxdWUgYW1iYXMgY3VydmFzIHNvbiBzaW1pbGFyZXMsIHBlcm8gZWwgbcOpdG9kbyAianVzdG8iIG1hbmVqYSBjb3JyZWN0YW1lbnRlIGxvcyBkYXRvcyBpbmNvbXBsZXRvcyBhbCBmaW5hbCBkZWwgcGVyw61vZG8gKE9jdCAyMDI0KSwgZG9uZGUgbm8gcHJvbWVkaWEgcG9ycXVlIG5vIGN1bXBsZSBsb3MgZmlsdHJvcyBkZSBjYWxpZGFkLg0KDQpgYGB7cn0NCiMgLS0tLSBQQVNPIDE6IENvbWJpbmFyIGxvcyBkYXRhIGZyYW1lcyAtLS0tDQojIFVzYW1vcyBiaW5kX3Jvd3MoKSBwYXJhIHVuaXJsb3MuDQojIC5pZCA9ICJtZXRvZG8iIGNyZWEgdW5hIG51ZXZhIGNvbHVtbmEgbGxhbWFkYSAnbWV0b2RvJyBxdWUgdG9tYSBlbCBub21icmUgcXVlIGxlIGRpbW9zIGEgY2FkYSBkYXRhZnJhbWUgKCJKdXN0byIgbyAiSW5nZW51byIpLg0KDQpkZl9jb21wYXJhdGl2byA8LSBiaW5kX3Jvd3MoDQogIEp1c3RvICAgPSBtb250aGx5X25hdGlvbmFsX2ZhaXIsDQogIEluZ2VudW8gPSBtb250aGx5X25hdGlvbmFsX3JhdywNCiAgLmlkID0gIm1ldG9kbyINCikNCg0KZ2dwbG90KGRmX2NvbXBhcmF0aXZvLCBhZXMoeCA9IHltLCB5ID0gdGVtcF9tZWFuLCBjb2xvciA9IG1ldG9kbykpICsNCiAgZ2VvbV9saW5lKGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKyANCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgUHJvbWVkaW8gTWVuc3VhbCBkZSBUZW1wZXJhdHVyYSBOYWNpb25hbCIsDQogICAgc3VidGl0bGUgPSAiTcOpdG9kbyAnSnVzdG8nIChwcm9tZWRpbyBkZSBlc3RhY2lvbmVzKSB2cy4gJ0luZ2VudW8nIChwcm9tZWRpbyBkZSBsZWN0dXJhcykiLA0KICAgIHggPSAiRmVjaGEiLA0KICAgIHkgPSAiVGVtcGVyYXR1cmEgTWVkaWEgKMKwQykiLA0KICAgIGNvbG9yID0gIk3DqXRvZG8gZGUgQ8OhbGN1bG8iDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMiB5ZWFycyIsIGRhdGVfbGFiZWxzID0gIiVZIikgDQpgYGANCg0KR3VhcmRhbW9zIGVzdG9zIGltcG9ydGFudGVzIGRhdGFmcmFtZXMgYWdyZWdhZG9zLg0KDQpgYGB7cn0NCnNhdmVSRFMoZGYsICJkZi5SZHMiKQ0Kc2F2ZVJEUyhkYWlseV9zdGF0aW9uLCAiZGFpbHlfc3RhdGlvbi5SZHMiKQ0Kc2F2ZVJEUyhtb250aGx5X2J5X3N0YXRpb25fZmFpciwgIm1vbnRobHlfYnlfc3RhdGlvbl9mYWlyLlJkcyIpDQpzYXZlUkRTKG1vbnRobHlfbmF0aW9uYWxfZmFpciwgIm1vbnRobHlfbmF0aW9uYWxfZmFpci5SZHMiKQ0KYGBgDQoNCg0KIyA2LiBBbsOhbGlzaXMgVmlzdWFsIEV4cGxvcmF0b3Jpbw0KDQpDb24gbG9zIGRhdG9zIGxpbXBpb3MgeSBhZ3JlZ2Fkb3MsIGNvbWVuemFtb3MgbGEgZXhwbG9yYWNpw7NuIHZpc3VhbC4NCg0KIyMgQmxvcXVlIDE6IENvbm9jaWVuZG8gZWwgQ29uanVudG8gZGUgRGF0b3MNCg0KUHJpbWVybywgdW5hIHZpc2nDs24gZ2VuZXJhbCBkZSBsYSBjb2JlcnR1cmEgZGUgZGF0b3MuDQoNCmBgYHtyfQ0KIyA9PT09PT09PT09PSBJTlRSTyBFRFVDQVRJVkEgKFDDmkJMSUNPIELDgVNJQ08pID09PT09PT09PT09PT0gDQojIC0tLS0gMSBmaWxhIHBvciBlc3RhY2nDs24gKHBhcmEgbWFwYSB5IGJhcnJhcykgLS0tLQ0Kc3RhdGlvbnNfdGJsIDwtIGRhaWx5X3N0YXRpb24gJT4lDQogIGZpbHRlcighaXMubmEobGF0aXR1ZCkgJiAhaXMubmEobG9uZ2l0dWQpKSAlPiUNCiAgZ3JvdXBfYnkobnJvKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KG5ybywgbm9tYnJlLCBwcm92aW5jaWEsIGxhdGl0dWQsIGxvbmdpdHVkKQ0KYGBgDQoNClVuIG1hcGEgaW50ZXJhY3Rpdm8gbm9zIHBlcm1pdGUgZXhwbG9yYXIgbGEgdWJpY2FjacOzbiBkZSBsYXMgZXN0YWNpb25lcywgY29sb3JlYWRhcyBwb3IgcHJvdmluY2lhLg0KYGBge3J9DQojID09PT09PT09PSBCTE9RVUUgMSDigJMgQ29ub2NpZW5kbyBlbCBjb25qdW50byBkZSBkYXRvcyA9PT09PT09PT09DQpwcm92aW5jaWFzX2dlbyA8LSBzZjo6c3RfcmVhZCgic2hpbnlfYXBwL2RhdGEvcHJvdmluY2lhcy5nZW9qc29uIikNCiMgVXNhbW9zICJTZXQzIiBvICJQYWlyZWQiIHF1ZSB0aWVuZW4gbXVjaG9zIGNvbG9yZXMgZGlzdGludG9zLg0KcGFsIDwtIGNvbG9yRmFjdG9yKA0KICBwYWxldHRlID0gIlNldDMiLCAjIFVuYSBwYWxldGEgY29uIGNvbG9yZXMgdmFyaWFkb3MgKGNvbW8gZW4gdHUgbWFwYSkNCiAgZG9tYWluID0gcHJvdmluY2lhc19nZW8kbm9tYnJlICMgQmFzYW1vcyBsb3MgY29sb3JlcyBlbiBsb3Mgbm9tYnJlcyBkZSBwcm92aW5jaWENCikNCg0KbV9pbnRybyA8LSBsZWFmbGV0KCkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICANCiAgIyBDQVBBIDE6IFBvbMOtZ29ub3MgZGUgUHJvdmluY2lhcyAoQUhPUkEgTk8gSU5URVJBQ1RJVk9TKQ0KICBhZGRQb2x5Z29ucygNCiAgICBkYXRhID0gcHJvdmluY2lhc19nZW8sDQogICAgZmlsbENvbG9yID0gfnBhbChub21icmUpLCANCiAgICB3ZWlnaHQgPSAxLA0KICAgIG9wYWNpdHkgPSAxLA0KICAgIGNvbG9yID0gIndoaXRlIiwNCiAgICBmaWxsT3BhY2l0eSA9IDAuNywNCiAgICANCiAgICAjIC0tLSBDQU1CSU8gQ0xBVkUgKFNpbnRheGlzIGNvcnJlY3RhKSAtLS0NCiAgICAjIERlc2FjdGl2YW1vcyBlbCBjbGljIHkgZWwgcmVzYWx0YWRvDQogICAgb3B0aW9ucyA9IHBhdGhPcHRpb25zKGludGVyYWN0aXZlID0gRkFMU0UpIA0KICApICU+JQ0KICANCiAgIyBDQVBBIDI6IE1hcmNhZG9yZXMgZGUgRXN0YWNpb25lcyAoRU5DSU1BIFkgQ0xJQ0FCTEVTKQ0KICBhZGRDaXJjbGVNYXJrZXJzKA0KICAgIGRhdGEgPSBzdGF0aW9uc190YmwsDQogICAgbG5nID0gfmxvbmdpdHVkLCANCiAgICBsYXQgPSB+bGF0aXR1ZCwNCiAgICByYWRpdXMgPSA0LCANCiAgICBzdHJva2UgPSBUUlVFLCANCiAgICB3ZWlnaHQgPSAxLA0KICAgIGNvbG9yID0gImJsYWNrIiwNCiAgICBmaWxsQ29sb3IgPSAid2hpdGUiLA0KICAgIGZpbGxPcGFjaXR5ID0gMC45LA0KICAgIA0KICAgICMgRWwgcG9wdXAgZGUgbGEgZXN0YWNpw7NuIGFob3JhIGZ1bmNpb25hcsOhDQogICAgcG9wdXAgPSB+c3ByaW50ZigiPGI+JXM8L2I+PGJyLz5Qcm92aW5jaWE6ICVzIiwgbm9tYnJlLCBwcm92aW5jaWEpDQogICkgJT4lDQogIA0KICBzZXRWaWV3KGxuZyA9IC02NCwgbGF0ID0gLTM4LjQsIHpvb20gPSA0KQ0KDQptX2ludHJvDQpgYGANCg0KVW4gZ3LDoWZpY28gZGUgYmFycmFzIGN1YW50aWZpY2EgZXN0bywgbW9zdHJhbmRvIGVsIG7Dum1lcm8gZGUgZXN0YWNpb25lcyBwb3IgcHJvdmluY2lhLg0KYGBge3J9DQojICgyKSBCYXJyYXM6IGNhbnRpZGFkIGRlIGVzdGFjaW9uZXMgcG9yIHByb3ZpbmNpYQ0KYmFyc19kZiA8LSBzdGF0aW9uc190YmwgJT4lDQogIGNvdW50KHByb3ZpbmNpYSwgbmFtZSA9ICJlc3RhY2lvbmVzIikgJT4lDQogIGFycmFuZ2UoZGVzYyhlc3RhY2lvbmVzKSkNCg0KIyAtLS0gR3LDoWZpY28gZ2dwbG90IG1vZGlmaWNhZG8gLS0tDQpwX2JhcnMgPC0gZ2dwbG90KGJhcnNfZGYsIGFlcyh4ID0gcmVvcmRlcihwcm92aW5jaWEsIGVzdGFjaW9uZXMpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGVzdGFjaW9uZXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gZXN0YWNpb25lcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyAtLS0gQcORQURFIEVTVEEgTMONTkVBIC0tLQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gcGFzdGUoIkVzdGFjaW9uZXM6IiwgZXN0YWNpb25lcykgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpICsNCiAgZ2VvbV9jb2woKSArDQogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiQmx1ZXMiLCBkaXJlY3Rpb24gPSAxKSArIA0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHggPSBOVUxMLCB5ID0gIkNhbnRpZGFkIGRlIGVzdGFjaW9uZXMiLCB0aXRsZSA9ICJFc3RhY2lvbmVzIHBvciBwcm92aW5jaWEiKSArDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTMpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIENvbnZlcnRpciBhIHBsb3RseQ0KcF9iYXJzIDwtIGdncGxvdGx5KHBfYmFycywgdG9vbHRpcCA9ICJ0ZXh0IikNCnBfYmFycw0KYGBgDQoNCkZpbmFsbWVudGUsIHVuYSBzZXJpZSB0ZW1wb3JhbCBtdWVzdHJhIGVsIG7Dum1lcm8gZGUgZXN0YWNpb25lcyBhY3RpdmFzIChxdWUgcmVwb3J0YW4gZGF0b3MpIHBvciBhw7FvLiBTZSBvYnNlcnZhIHVuYSBub3RhYmxlIGNhw61kYSBlbiBsb3MgYcOxb3MgbcOhcyByZWNpZW50ZXMgZGVsIGNvbmp1bnRvIGRlIGRhdG9zLg0KYGBge3J9DQojICgzKSBUaW1lbGluZTogZXN0YWNpb25lcyBhY3RpdmFzIHBvciBhw7FvIChzaW1wbGUpDQojIChUdSBjw7NkaWdvIGRlIHByZXBhcmFjacOzbiBkZSBkYXRvcykNCnllYXJzX2FjdGl2ZSA8LSBkYWlseV9zdGF0aW9uICU+JQ0KICBncm91cF9ieSh5ZWFyKSAlPiUNCiAgc3VtbWFyaXNlKGVzdGFjaW9uZXNfYWN0aXZhcyA9IG5fZGlzdGluY3Qobm9tYnJlKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIGFycmFuZ2UoeWVhcikNCg0KIyAtLS0gQ8OhbGN1bG8gZGluw6FtaWNvIGRlIGxvcyBsw61taXRlcyBkZWwgem9vbSAtLS0NCm1pbl92YWwgPC0gbWluKHllYXJzX2FjdGl2ZSRlc3RhY2lvbmVzX2FjdGl2YXMsIG5hLnJtID0gVFJVRSkNCm1heF92YWwgPC0gbWF4KHllYXJzX2FjdGl2ZSRlc3RhY2lvbmVzX2FjdGl2YXMsIG5hLnJtID0gVFJVRSkNCnBhZGRpbmcgPC0gKG1heF92YWwgLSBtaW5fdmFsKSAqIDAuMg0KDQpwX3RpbWVsaW5lX3pvb20gPC0gZ2dwbG90KHllYXJzX2FjdGl2ZSwgYWVzKHllYXIsIGVzdGFjaW9uZXNfYWN0aXZhcykpICsNCiAgZ2VvbV9hcmVhKGZpbGwgPSAiI2Q2MjcyOCIsIGFscGhhID0gMC4zKSArIA0KICBnZW9tX2xpbmUoY29sb3IgPSAiI2Q2MjcyOCIsIGxpbmV3aWR0aCA9IDEuMikgKw0KICANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHByZXR0eV9icmVha3MoKSkgKw0KICANCiAgbGFicygNCiAgICB4ID0gIkHDsW8iLCB5ID0gIkVzdGFjaW9uZXMgYWN0aXZhcyIsIA0KICAgIHRpdGxlID0gIkNhw61kYSBSZWNpZW50ZSBlbiBlbCBDb250ZW8gZGUgRXN0YWNpb25lcyBBY3RpdmFzIiwNCiAgICBzdWJ0aXRsZSA9ICJOb3RhOiBFbCBlamUgWSAodmVydGljYWwpIG5vIGluaWNpYSBlbiAwIHBhcmEgZW5mYXRpemFyIGxhIHZhcmlhY2nDs24uIg0KICApICsNCiAgDQogICMgLS0tIExBIFBBUlRFIENMQVZFIC0tLQ0KICAjIEhhY2Vtb3Mgem9vbSBlbiBlbCBlamUgWS4gTG9zIGzDrW1pdGVzIHNlIGNhbGN1bGFuIGRpbsOhbWljYW1lbnRlLg0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMobWluX3ZhbCAtIHBhZGRpbmcsIG1heF92YWwgKyBwYWRkaW5nKSkgKw0KICANCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMykNCg0KcF90aW1lbGluZV96b29tIDwtIGdncGxvdGx5KHBfdGltZWxpbmVfem9vbSwgdG9vbHRpcCA9IGMoIngiLCAieSIpKQ0KcF90aW1lbGluZV96b29tDQpgYGANCg0KIyMgQmxvcXVlIDI6IMK/UXXDqSB2YXJpYWJsZXMgc2UgcmVnaXN0cmFyb24/DQoNClVuYSB0YWJsYSBzaW1wbGUgZGUgS1BJcyAoSW5kaWNhZG9yZXMgQ2xhdmUgZGUgUmVuZGltaWVudG8pIG5vcyBkYSBsb3MgcHJvbWVkaW9zIGdlbmVyYWxlcyBkZWwgZGF0YXNldC4NCmBgYHtyfQ0KIyA9PT09PT09PSBCTE9RVUUgMiDigJMgwr9RdcOpIHZhcmlhYmxlcyBzZSByZWdpc3RyYXJvbj8gPT09PT09PT09DQoNCiMgKDQpIEtQSXMgc2VuY2lsbG9zICh0YWJsYSkg4oCUIHNlIGltcHJpbWUgZW4gZWwgUm1kDQprcGlzIDwtIGRhaWx5X3N0YXRpb24gJT4lDQogIHN1bW1hcmlzZSgNCiAgICAjIEVudnVlbHZlIGxvcyBub21icmVzIGNvbiBlc3BhY2lvcyB5IGNhcmFjdGVyZXMgZXNwZWNpYWxlcyBlbiBgYmFja3RpY2tzYA0KICAgIGBUZW1wX21lZGlhICjCsEMpYCAgICAgICAgICA9IG1lYW4odGVtcF9tZWFuLCBuYS5ybSA9IFRSVUUpLA0KICAgIGBIdW1lZGFkX21lZGlhICglKWAgICAgICAgID0gbWVhbihodW1fbWVhbiwgbmEucm0gPSBUUlVFKSwNCiAgICBgUHJlc2nDs25fbWVkaWEgKGhQYSlgICAgICAgPSBtZWFuKHBubV9tZWFuLCBuYS5ybSA9IFRSVUUpLA0KICAgIGBMbHV2aWFfZGlhcmlhX21lZGlhIChtbSlgID0gbWVhbihwcmVjaXBfc3VtLCBuYS5ybSA9IFRSVUUpDQogICkNCg0Ka3Bpc190YWJsZSA8LSBrbml0cjo6a2FibGUoa3BpcywgZGlnaXRzID0gMSwgYWxpZ24gPSAiciIpDQprcGlzX3RhYmxlDQpgYGANCg0KTHVlZ28sIGdlbmVyYW1vcyBoaXN0b2dyYW1hcyBwYXJhIHZpc3VhbGl6YXIgbGEgZGlzdHJpYnVjacOzbiBkZSBsYXMgcHJpbmNpcGFsZXMgdmFyaWFibGVzIGRpYXJpYXM6IFRlbXBlcmF0dXJhLCBIdW1lZGFkIHkgUHJlc2nDs24uDQpgYGB7cn0NCiMgKDUpIEhpc3RvZ3JhbWFzIHNpbXBsZXMgcG9yIHZhcmlhYmxlDQpta19oaXN0IDwtIGZ1bmN0aW9uKGRmLCB2YXIsIHRpdHVsbywgeGxhYiwgDQogICAgICAgICAgICAgICAgICAgIGJpbnMgPSA0MCwgDQogICAgICAgICAgICAgICAgICAgIGZpbGxfY29sb3IgPSAiIzFmNzdiNCIsICMgPC0tIDEuIE51ZXZvIGFyZ3VtZW50byBjb24gdW4gY29sb3IgYXp1bA0KICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuOCkgeyAgICAgICAgICAjIDwtLSAyLiBOdWV2byBhcmd1bWVudG8gcGFyYSB0cmFuc3BhcmVuY2lhDQogIA0KICBnZyA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gLmRhdGFbW3Zhcl1dKSkgKw0KICAgIGdlb21faGlzdG9ncmFtKA0KICAgICAgYmlucyA9IGJpbnMsIA0KICAgICAgYm91bmRhcnkgPSAwLCANCiAgICAgIGNvbG9yID0gIndoaXRlIiwgICAgICAjIE1hbnRpZW5lIGVsIGJvcmRlIGJsYW5jbw0KICAgICAgZmlsbCA9IGZpbGxfY29sb3IsICAgICMgPC0tIDMuIFVzYSBlbCBjb2xvciBkZSByZWxsZW5vDQogICAgICBhbHBoYSA9IGFscGhhICAgICAgICAgIyA8LS0gNC4gQXBsaWNhIGxhIHRyYW5zcGFyZW5jaWENCiAgICApICsNCiAgICBsYWJzKHRpdGxlID0gdGl0dWxvLCB4ID0geGxhYiwgeSA9ICJGcmVjdWVuY2lhIikgKw0KICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTMpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICMgT2N1bHRhIGxhIGxleWVuZGEgKG5vIG5lY2VzYXJpYSkNCiAgDQogIGdncGxvdGx5KGdnLCB0b29sdGlwID0gYygieCIsInkiKSkNCn0NCmBgYA0KDQpgYGB7cn0NCmRpc3RfdGVtcCA8LSBta19oaXN0KGRhaWx5X3N0YXRpb24sICJ0ZW1wX21lYW4iLCAgIsK/Q8OzbW8gc2UgcmVwYXJ0ZSBsYSB0ZW1wZXJhdHVyYT8iLCAiVGVtcGVyYXR1cmEgKMKwQykiKQ0KZGlzdF90ZW1wDQoNCmRpc3RfaHVtIDwtIG1rX2hpc3QoZGFpbHlfc3RhdGlvbiwgImh1bV9tZWFuIiwgICAiwr9Dw7NtbyBzZSByZXBhcnRlIGxhIGh1bWVkYWQ/IiwgIkh1bWVkYWQgcmVsYXRpdmEgKCUpIikNCmRpc3RfaHVtDQoNCmRpc3RfcG5tIDwtIG1rX2hpc3QoZGFpbHlfc3RhdGlvbiwgInBubV9tZWFuIiwgICAiwr9Dw7NtbyBzZSByZXBhcnRlIGxhIHByZXNpw7NuPyIsICJQcmVzacOzbiAoaFBhKSIpDQpkaXN0X3BubQ0KYGBgDQoNCiMjIEJsb3F1ZSAzOiBVbmEgbWlyYWRhIHLDoXBpZGEgYSBsb3MgcGF0cm9uZXMNCg0Kwr9Dw7NtbyBpbnRlcmFjdMO6YW4gZXN0YXMgdmFyaWFibGVzPw0KDQpFc3RlIGdyw6FmaWNvIG11ZXN0cmEgZWwgY2ljbG8gZXN0YWNpb25hbCBwcm9tZWRpbyBkZSB0ZW1wZXJhdHVyYS4gTG9zIHB1bnRvcyBlc3TDoW4gY29sb3JlYWRvcyBwYXJhIG1vc3RyYXIgY2xhcmFtZW50ZSBsb3MgbWVzZXMgZnLDrW9zIChhenVsKSB5IGPDoWxpZG9zIChyb2pvKS4NCmBgYHtyfQ0KIyA9PT09PT09PT09PT09PT09PT09PT0gQkxPUVVFIDMg4oCTIFVuYSBtaXJhZGEgcsOhcGlkYSBhIGxvcyBwYXRyb25lcyA9PT09PT09PT09PT09PT09PT09PT0NCg0KIyAoNikgU2VyaWUgbWVuc3VhbCBwcm9tZWRpbyBuYWNpb25hbCAobXV5IHNpbXBsZSkNCiMgKDEpIFByZXBhcmFjacOzbiBkZSBkYXRvcyAoaWd1YWwgcXVlIHR1IGPDs2RpZ28pDQptb250aGx5X21lYW4gPC0gZGFpbHlfc3RhdGlvbiAlPiUNCiAgZ3JvdXBfYnkobW9udGgpICU+JQ0KICBzdW1tYXJpc2UodGVtcCA9IG1lYW4odGVtcF9tZWFuLCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgYXJyYW5nZShtb250aCkgJT4lDQogIG11dGF0ZShtZXMgPSBmYWN0b3IobW9udGguYWJiW21vbnRoXSwgbGV2ZWxzID0gbW9udGguYWJiKSkNCg0KIyAoMikgQ2FsY3VsYXIgZWwgcHVudG8gbWVkaW8gZGUgbGEgdGVtcGVyYXR1cmEgcGFyYSBjZW50cmFyIGxhIHBhbGV0YQ0KbWlkX3RlbXAgPC0gbWVhbihtb250aGx5X21lYW4kdGVtcCwgbmEucm0gPSBUUlVFKQ0KDQojIC0tLSAoMykgR3LDoWZpY28gZ2dwbG90IE1lam9yYWRvIC0tLQ0KcF9tb250aCA8LSBnZ3Bsb3QobW9udGhseV9tZWFuLCBhZXMoeCA9IG1lcywgeSA9IHRlbXAsIGdyb3VwID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IHBhc3RlMCgiTWVzOiAiLCBtZXMsICI8YnI+VGVtcDogIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ByaW50ZigiJS4xZiIsIHRlbXApLCAiwrBDIikNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkgKw0KICANCiAgIyAtLS0gQ0FNQklPIEFRVcONIC0tLQ0KICAjIDEuIExhIGzDrW5lYSBhaG9yYSBlcyBkZSB1biBjb2xvciBncmlzIGVzdMOhdGljby4NCiAgZ2VvbV9saW5lKGNvbG9yID0gImdyZXk4MCIsIGxpbmV3aWR0aCA9IDEuMikgKyANCiAgDQogICMgMi4gTG9zIHB1bnRvcyBTw40gbGxldmFuIGVsIGNvbG9yIGRpbsOhbWljby4NCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSB0ZW1wKSwgc2l6ZSA9IDMpICsNCiAgIyAtLS0gRklOIERFTCBDQU1CSU8gLS0tDQogIA0KICBzY2FsZV9jb2xvcl9ncmFkaWVudDIoDQogICAgbG93ID0gImRvZGdlcmJsdWU0IiwgIA0KICAgIG1pZCA9ICJ3aGl0ZSIsICAgICAgICANCiAgICBoaWdoID0gImZpcmVicmljayIsICAgICANCiAgICBtaWRwb2ludCA9IG1pZF90ZW1wICAgDQogICkgKw0KICANCiAgbGFicyh4ID0gIk1lcyIsIHkgPSAiVGVtcGVyYXR1cmEgbWVkaWEgKMKwQykiLCANCiAgICAgICB0aXRsZSA9ICJDaWNsbyBFc3RhY2lvbmFsIGRlIFRlbXBlcmF0dXJhIikgKw0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEzKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyAtLS0gKDQpIGdncGxvdGx5IGNvbiB0b29sdGlwIHBlcnNvbmFsaXphZG8gLS0tDQpwX21vbnRoIDwtIGdncGxvdGx5KHBfbW9udGgsIHRvb2x0aXAgPSAidGV4dCIpDQpwX21vbnRoDQpgYGANCg0KVXNhbW9zIHVuIGdyw6FmaWNvIGRlIGhleMOhZ29ub3MgKGdlb21faGV4KSBwYXJhIHZpc3VhbGl6YXIgbGEgZGVuc2lkYWQgZGUgbGEgcmVsYWNpw7NuIGVudHJlIHRlbXBlcmF0dXJhIHkgaHVtZWRhZC4gTGFzIMOhcmVhcyBtw6FzIG9zY3VyYXMgaW5kaWNhbiBsYXMgY29uZGljaW9uZXMgbcOhcyBmcmVjdWVudGVzLiBMYSBsw61uZWEgZGUgcmVncmVzacOzbiAocm9qYSkgY29uZmlybWEgbGEgY29ycmVsYWNpw7NuIG5lZ2F0aXZhOiBhIG3DoXMgY2Fsb3IsIG1lbm9zIGh1bWVkYWQgcmVsYXRpdmEuDQpgYGB7cn0NCiMgKDcpIFJlbGFjacOzbiBzaW1wbGU6IFRlbXBlcmF0dXJhIHZzIEh1bWVkYWQgKG11ZXN0cmEgcGFyYSBubyBzb2JyZWNhcmdhcikNCnNldC5zZWVkKDEyMykNCmRfc2FtcCA8LSBkYWlseV9zdGF0aW9uICU+JQ0KICBzZWxlY3QodGVtcF9tZWFuLCBodW1fbWVhbikgJT4lDQogIHRpZHlyOjpkcm9wX25hKCkNCmlmIChucm93KGRfc2FtcCkgPiAzMDAwKSBkX3NhbXAgPC0gZF9zYW1wICU+JSBkcGx5cjo6c2xpY2Vfc2FtcGxlKG4gPSAzMDAwKQ0KDQojIC0tLSBHcsOhZmljbyBjb24gZ2VvbV9oZXggLS0tDQpwX3NjX2hleCA8LSBnZ3Bsb3QoZF9zYW1wLCBhZXMoeCA9IHRlbXBfbWVhbiwgeSA9IGh1bV9tZWFuKSkgKw0KICANCiAgIyAxLiBSZWVtcGxhemFtb3MgZ2VvbV9wb2ludCBwb3IgZ2VvbV9oZXgNCiAgZ2VvbV9oZXgoYmlucyA9IDUwKSArICMgJ2JpbnMnIGNvbnRyb2xhIGVsIHRhbWHDsW8gZGUgbG9zIGhleMOhZ29ub3MNCiAgDQogICMgMi4gQcOxYWRpbW9zIHVuYSBlc2NhbGEgZGUgY29sb3IgKGZpbGwpDQogICMgJ3RyYW5zID0gImxvZzEwIicgZXMgY2xhdmU6IGV2aXRhIHF1ZSBsb3MgcHVudG9zIG3DoXMgZGVuc29zICJzYXR1cmVuIiBsYSBlc2NhbGENCiAgc2NhbGVfZmlsbF9ncmFkaWVudCgNCiAgICBsb3cgPSAibGlnaHRibHVlIiwgDQogICAgaGlnaCA9ICJuYXZ5Ymx1ZSIsIA0KICAgIHRyYW5zID0gImxvZzEwIiwgDQogICAgbmFtZSA9ICJDb250ZW9zIiAjIFTDrXR1bG8gZGUgbGEgbGV5ZW5kYQ0KICApICsNCiAgDQogICMgMy4gTWFudGVuZW1vcyBsYSBsw61uZWEgZGUgcmVncmVzacOzbiAoZW4gcm9qbyBwYXJhIHF1ZSByZXNhbHRlKQ0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiLCBsaW5ld2lkdGggPSAxKSArDQogIA0KICBsYWJzKHggPSAiVGVtcGVyYXR1cmEgKMKwQykiLCB5ID0gIkh1bWVkYWQgcmVsYXRpdmEgKCUpIiwNCiAgICAgICB0aXRsZSA9ICJEZW5zaWRhZCBkZSBsYSBSZWxhY2nDs24gVGVtcGVyYXR1cmEtSHVtZWRhZCIpICsNCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMykNCg0KIyA0LiBnZ3Bsb3RseSAoc2luICd0b29sdGlwJykgcGFyYSBxdWUgbXVlc3RyZSBlbCBjb250ZW8gYXV0b23DoXRpY2FtZW50ZQ0KcF9zY19oZXggPC0gZ2dwbG90bHkocF9zY19oZXgpDQpwX3NjX2hleA0KYGBgDQoNCkFjw6EgdXNhbW9zIGxvcyBkYXRvcyBkZWwgZGYgb3JpZ2luYWwgKGhvcmFyaW9zKSBwYXJhIHVuIGFuw6FsaXNpcyBkZSBkZW5zaWRhZCBtw6FzIHByb2Z1bmRvLg0KDQpVbiBncsOhZmljbyBkZSBjb250b3JubyAyRCBjb25maXJtYSBsYSByZWxhY2nDs24gaW52ZXJzYSBlbnRyZSB0ZW1wZXJhdHVyYSB5IGh1bWVkYWQuDQpgYGB7cn0NCiMgPT09PT09PT09PT09PT09PT09PT0gQ09SUkVMQUNJT05FUyA9PT09PT09PT09PT09PT09PT09PT09PT09PQ0Kc2V0LnNlZWQoMTIzKQ0KZGZfc21wbCA8LSBkZiAlPiUNCiAgc2xpY2Vfc2FtcGxlKG4gPSAzMDAwMDApDQoNCnBfdGhfY29udG91ciA8LSBwbG90X2x5KGRmX3NtcGwsIHggPSB+dGVtcCwgeSA9IH5odW0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJoaXN0b2dyYW0yZGNvbnRvdXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JzY2FsZSA9ICJIb3QiLA0KICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JiYXIgPSBsaXN0KHRpdGxlID0gIjxiPk5yby4gZGU8YnI+T2JzZXJ2YWNpb25lczwvYj4iKQ0KICAgICAgICAgICAgICAgICAgICAgICApICU+JQ0KICBsYXlvdXQoDQogICAgIyAtLS0gTU9ESUZJQ0FDScOTTiAyOiBUw610dWxvcyBtw6FzIGRlc2NyaXB0aXZvcyAtLS0NCiAgICB0aXRsZSA9IGxpc3QodGV4dCA9ICJSZWxhY2nDs24gZW50cmUgdGVtcGVyYXR1cmEgeSBodW1lZGFkIHJlbGF0aXZhPGJyPjxzdXA+QSBtYXlvciB0ZW1wZXJhdHVyYSwgdGllbmRlIGEgaGFiZXIgbWVub3IgaHVtZWRhZC4gRGVuc2lkYWQgMkQgZGUgMzAwayBvYnMuPC9zdXA+IiksDQogICAgDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlRlbXBlcmF0dXJhICjCsEMpIiksDQogICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkh1bWVkYWQgcmVsYXRpdmEgKCUpIiksDQogICAgDQogICAgIyAtLS0gTU9ESUZJQ0FDScOTTiAzOiBBcnJlZ2xhciBlbCBtYXJnZW4gc3VwZXJpb3IgLS0tDQogICAgbWFyZ2luID0gbGlzdCgNCiAgICAgICAgYiA9IDgwLCAjIE1hcmdlbiBpbmZlcmlvciBwYXJhIGxhICJGdWVudGUiDQogICAgICAgIHQgPSA4MCAgIyBOdWV2byBtYXJnZW4gc3VwZXJpb3IgcGFyYSBxdWUgZW50cmUgZWwgdMOtdHVsby9zdWJ0w610dWxvDQogICAgKSwNCiAgICANCiAgICBhbm5vdGF0aW9ucyA9IGxpc3QoDQogICAgICBsaXN0KHRleHQgPSAiRnVlbnRlOiBTTU4g4oCTIGVsYWJvcmFjacOzbiBwcm9waWEuIiwNCiAgICAgICAgICAgc2hvd2Fycm93ID0gRkFMU0UsIHhyZWYgPSAicGFwZXIiLCB5cmVmID0gInBhcGVyIiwgeCA9IDEsIHkgPSAtMC4xNSwNCiAgICAgICAgICAgeGFuY2hvciA9ICdyaWdodCcsIHlhbmNob3IgPSAnYXV0bycsIGZvbnQgPSBsaXN0KHNpemUgPSAxMCkpDQogICAgKQ0KICApDQoNCnBfdGhfY29udG91cg0KYGBgDQoNCkxvIG1pc21vIHBlcm8gZW50cmUgdGVtcGVyYXR1cmEgeSBwcmVzacOzbi4NCg0KRXN0ZSBoaXN0b2dyYW1hIDJEIG11ZXN0cmEgcXVlIGxhcyB0ZW1wZXJhdHVyYXMgbcOhcyBhbHRhcyB0aWVuZGVuIGEgY29pbmNpZGlyIGNvbiBwcmVzaW9uZXMgYXRtb3Nmw6lyaWNhcyBsaWdlcmFtZW50ZSBtZW5vcmVzLCBjb24gdW5hIGdyYW4gY29uY2VudHJhY2nDs24gZGUgZGF0b3MgZW50cmUgMTAwOCB5IDEwMjIgaFBhLg0KYGBge3J9DQojIC0tLS0gSGlzdG9ncmFtYSBkZSBEZW5zaWRhZDogVEVNUCB2cyBQTk0gLS0tLQ0KDQpwX3RwIDwtIHBsb3RfbHkoZGZfc21wbCwgeCA9IH50ZW1wLCB5ID0gfnBubSwgdHlwZSA9ICJoaXN0b2dyYW0yZCIpICU+JQ0KICANCiAgIyBVc2FyIHBsb3RseTo6bGF5b3V0IHBhcmEgYWdydXBhciBUT0RBIGxhIGNvbmZpZ3VyYWNpw7NuDQogIHBsb3RseTo6bGF5b3V0KA0KICAgIA0KICAgIHRpdGxlID0gbGlzdCh0ZXh0ID0gIlRlbXBlcmF0dXJhcyBtw6FzIGFsdGFzIHRpZW5kZW4gYSBjb2luY2lkaXIgY29uIHByZXNpb25lcyBhdG1vc2bDqXJpY2FzIGxpZ2VyYW1lbnRlIG1lbm9yZXM8YnI+PHN1cD5EZW5zaWRhZCAyRCBjb24gY29uY2VudHJhY2nDs24gZW4gZWwgcmFuZ28gMTAwOOKAkzEwMjIgaFBhPC9zdXA+IiksDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlRlbXBlcmF0dXJhICjCsEMpIiksDQogICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlByZXNpw7NuIGEgTml2ZWwgZGVsIE1hciAoaFBhKSIpLA0KICAgIA0KICAgICMgLS0tIE1PRElGSUNBQ0nDk046ICdjb2xvcmJhcicgYWhvcmEgdml2ZSBERU5UUk8gZGUgbGF5b3V0IC0tLQ0KICAgIGNvbG9yYmFyID0gbGlzdCh0aXRsZSA9ICI8Yj5EZW5zaWRhZCBkZSBIb3JhczwvYj4iKSwgIyBMZSBhZ3JlZ3XDqSBuZWdyaXRhDQogICAgDQogICAgIyBFbCBtYXJnZW4gcXVlIHNvbHVjaW9uYSBlbCBjb3J0ZSBkZWwgdMOtdHVsbw0KICAgIG1hcmdpbiA9IGxpc3QoYiA9IDgwLCB0ID0gMTAwKSwgDQogICAgDQogICAgYW5ub3RhdGlvbnMgPSBsaXN0KA0KICAgICAgbGlzdCh0ZXh0ID0gIkZ1ZW50ZTogU01OIOKAkyBlbGFib3JhY2nDs24gcHJvcGlhLiIsDQogICAgICAgICAgIHNob3dhcnJvdyA9IEZBTFNFLCB4cmVmID0gInBhcGVyIiwgeXJlZiA9ICJwYXBlciIsIHggPSAxLCB5ID0gLTAuMTUsDQogICAgICAgICAgIHhhbmNob3IgPSAncmlnaHQnLCB5YW5jaG9yID0gJ2F1dG8nLCBmb250ID0gbGlzdChzaXplID0gMTApKQ0KICAgICkNCiAgKQ0KDQojIERlamFyIHF1ZSBlbCBub3RlYm9vayByZW5kZXJpY2UgZWwgb2JqZXRvDQpwX3RwDQpgYGANCg0KIyMgQmxvcXVlIDQ6IEFuw6FsaXNpcyBHZW9lc3BhY2lhbCAoQ29yb3BsZXRhcykNCg0KQ3JlYW1vcyB1biBtYXBhIGRlIGNvcm9wbGV0YXMgKG1hcGEgZGUgcG9sw61nb25vcyBjb2xvcmVhZG9zKSBwYXJhIHZpc3VhbGl6YXIgbGEgdGVtcGVyYXR1cmEgbWVkaWEgcG9yIHByb3ZpbmNpYSwgcmV2ZWxhbmRvIGVsIGNsYXJvIGdyYWRpZW50ZSB0w6lybWljbyBkZSBub3J0ZSBhIHN1ci4NCmBgYHtyfQ0KDQojID09PT09PT0gKE9wY2lvbmFsKSBCTE9RVUUgNCDigJMgTWFwYSBzaW1wbGUgcG9yIHByb3ZpbmNpYSA9PT09PT09PQ0KDQpzdGF0aW9uX2NsaW1hIDwtIGRhaWx5X3N0YXRpb24gJT4lDQogICAgZ3JvdXBfYnkobnJvLCBwcm92aW5jaWEpICU+JQ0KICAgIHN1bW1hcmlzZSh0ZW1wX2Jhc2UgPSBtZWFuKHRlbXBfbWVhbiwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KcHJvdl90ZW1wIDwtIHN0YXRpb25fY2xpbWEgJT4lDQogICAgZ3JvdXBfYnkocHJvdmluY2lhKSAlPiUNCiAgICBzdW1tYXJpc2UodGVtcF9wcm92ID0gbWVhbih0ZW1wX2Jhc2UsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIHByb3ZpbmNpYV9qb2luX2tleSA9IHRvdXBwZXIoaWNvbnYocHJvdmluY2lhLCBmcm9tID0gIlVURi04IiwgdG8gPSAiQVNDSUkvL1RSQU5TTElUIikpLA0KICAgICAgcHJvdmluY2lhX2pvaW5fa2V5ID0gZHBseXI6OmNhc2Vfd2hlbigNCiAgICAgICAgcHJvdmluY2lhX2pvaW5fa2V5ID09ICJDQVBJVEFMIEZFREVSQUwiIH4gIkNBQkEiLA0KICAgICAgICBwcm92aW5jaWFfam9pbl9rZXkgPT0gIlRJRVJSQSBERUwgRlVFR08iIH4gIlRJRVJSQSBERUwgRlVFR08iLA0KICAgICAgICBUUlVFIH4gcHJvdmluY2lhX2pvaW5fa2V5DQogICAgICApDQogICAgKQ0KcHJvdmluY2lhc19nZW8gPC0gcHJvdmluY2lhc19nZW8gJT4lDQogIG11dGF0ZSgNCiAgICBwcm92aW5jaWFfam9pbl9rZXkgPSB0b3VwcGVyKGljb252KG5vbWJyZSwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIkFTQ0lJLy9UUkFOU0xJVCIpKSwNCiAgICBwcm92aW5jaWFfam9pbl9rZXkgPSBkcGx5cjo6Y2FzZV93aGVuKA0KICAgICAgcHJvdmluY2lhX2pvaW5fa2V5ID09ICJDQVBJVEFMIEZFREVSQUwiIH4gIkNBQkEiLA0KICAgICAgcHJvdmluY2lhX2pvaW5fa2V5ID09ICJDSVVEQUQgQVVUT05PTUEgREUgQlVFTk9TIEFJUkVTIiB+ICJDQUJBIiwNCiAgICAgIHByb3ZpbmNpYV9qb2luX2tleSA9PSAiVElFUlJBIERFTCBGVUVHTywgQU5UQVJUSURBIEUgSVNMQVMgREVMIEFUTEFOVElDTyBTVVIiIH4gIlRJRVJSQSBERUwgRlVFR08iLA0KICAgICAgVFJVRSB+IHByb3ZpbmNpYV9qb2luX2tleQ0KICAgICkNCiAgKQ0KDQptYXBfZGYgPC0gcHJvdmluY2lhc19nZW8gJT4lDQogICAgbGVmdF9qb2luKHByb3ZfdGVtcCwgYnkgPSBjKCJwcm92aW5jaWFfam9pbl9rZXkiKSkNCg0KICBwYWwgPC0gY29sb3JOdW1lcmljKCJSZFlsQnUiLCBkb21haW4gPSBtYXBfZGYkdGVtcF9wcm92LCByZXZlcnNlID0gVFJVRSwgbmEuY29sb3IgPSAiI0JEQkRCRCIpDQoNCm1fY2hvcm8gPC0gbGVhZmxldChtYXBfZGYpIHw+DQogICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgfD4NCiAgICBhZGRQb2x5Z29ucygNCiAgICAgIGZpbGxDb2xvciA9IH5wYWwodGVtcF9wcm92KSwgY29sb3IgPSAid2hpdGUiLCB3ZWlnaHQgPSAxLCBvcGFjaXR5ID0gMSwNCiAgICAgIGZpbGxPcGFjaXR5ID0gMC43LA0KICAgICAgbGFiZWwgPSB+aHRtbHRvb2xzOjpIVE1MKHNwcmludGYoIjxiPiVzPC9iPjxici8+VGVtcCBtZWRpYTogJXMgwrBDIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vbWJyZSwgaWZlbHNlKGlzLm5hKHRlbXBfcHJvdiksICJzL2QiLCBzcHJpbnRmKCIlLjFmIiwgdGVtcF9wcm92KSkpKQ0KICAgICkgfD4NCiAgICBhZGRMZWdlbmQoImJvdHRvbXJpZ2h0IiwgcGFsID0gcGFsLCB2YWx1ZXMgPSB+dGVtcF9wcm92LA0KICAgICAgICAgICAgICB0aXRsZSA9ICJUZW1wIG1lZGlhICjCsEMpIiwgb3BhY2l0eSA9IDEpIHw+DQogICAgc2V0VmlldyhsbmcgPSAtNjQsIGxhdCA9IC0zOC40LCB6b29tID0gNCkNCm1fY2hvcm8NCg0KDQojID09PT09PT09PT09PT09PT09PT09PSBGSU4gSU5UUk8gRURVQ0FUSVZBIChkcGx5ciwgc2luIHNhdmVzKSA9PT09PT09PT09PT09PT09PT09PT0NCmBgYA0KDQpBIGNvbnRpbnVhY2nDs24sIGNyZWFtb3MgdW4gbWFwYSBpbnRlcmFjdGl2byBtdWx0aWNhcGEgbXVjaG8gbcOhcyBhdmFuemFkby4gRXN0ZSBtYXBhIHBlcm1pdGUgYWwgdXN1YXJpbyBzdXBlcnBvbmVyIHkgYWx0ZXJuYXIgZW50cmUgY3VhdHJvIHZpc3RhcyBkaWZlcmVudGVzOg0KLSBFc3RhY2lvbmVzIChUZW1wIG1lZGlhKTogUHVudG9zIGNvbG9yZWFkb3MgcG9yIHRlbXBlcmF0dXJhIG1lZGlhLg0KLSBFc3RhY2lvbmVzIChBbXBsaXR1ZCk6IFB1bnRvcyBjb2xvcmVhZG9zIHBvciBhbXBsaXR1ZCB0w6lybWljYSAoZGlmZXJlbmNpYSBQOTAtUDEwKS4NCi0gUHJvdmluY2lhcyAoVGVtcCBtZWRpYSk6IEVsIG1hcGEgZGUgY29yb3BsZXRhcyBxdWUgYWNhYmFtb3MgZGUgdmVyLg0KLSBQcm92aW5jaWFzIChBbXBsaXR1ZCk6IFVuIG1hcGEgZGUgY29yb3BsZXRhcyBkZSBsYSBhbXBsaXR1ZCB0w6lybWljYS4NCkVzdG8gcHJvcG9yY2lvbmEgdW5hIGhlcnJhbWllbnRhIG11eSByaWNhIHBhcmEgZXhwbG9yYXIgcGF0cm9uZXMgZ2VvZXNwYWNpYWxlcy4NCg0KYGBge3J9DQojIC0tLSAxLiBQUkVQQVJBQ0nDk04gREUgREFUT1MgKFR1IGPDs2RpZ28gb3JpZ2luYWwpIC0tLS0NCg0KIyAoRXh0cmVtb3MpDQpleHQgPC0gZGYgJT4lDQogIGZpbHRlcighaXMubmEodGVtcCkpICU+JQ0KICBncm91cF9ieShucm8sIG5vbWJyZSwgcHJvdmluY2lhLCBsYXRpdHVkLCBsb25naXR1ZCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBwMTAgPSBxdWFudGlsZSh0ZW1wLCAuMTAsIG5hLnJtID0gVFJVRSksDQogICAgcDkwID0gcXVhbnRpbGUodGVtcCwgLjkwLCBuYS5ybSA9IFRSVUUpLA0KICAgIGFtcCA9IHA5MCAtIHAxMCwgDQogICAgLmdyb3VwcyA9ICdkcm9wJyANCiAgKQ0KDQojIChSZXN1bWVuIGRlIGVzdGFjaW9uZXMpDQpzdGF0aW9ucyA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkobnJvLCBub21icmUsIHByb3ZpbmNpYSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBsYXQgPSBmaXJzdChsYXRpdHVkKSwNCiAgICBsb24gPSBmaXJzdChsb25naXR1ZCksDQogICAgdGVtcF9tZWFuID0gbWVhbih0ZW1wLCBuYS5ybSA9IFRSVUUpLA0KICAgIHNkX3RlbXAgPSBzZCh0ZW1wLCBuYS5ybSA9IFRSVUUpLA0KICAgIHJlZ2lzdHJvcyA9IG4oKSwNCiAgICAuZ3JvdXBzID0gJ2Ryb3AnDQogICkgJT4lDQogIGZpbHRlcighaXMubmEobGF0KSAmICFpcy5uYShsb24pICYgIWlzLm5hKHRlbXBfbWVhbikpDQoNCiMgLS0tLSAyLiBQUkUtQ8OBTENVTE8gREVMIFJBRElPIChUdSBjw7NkaWdvIG9yaWdpbmFsKSAtLS0tDQpzdGF0aW9ucyA8LSBzdGF0aW9ucyAlPiUNCiAgbXV0YXRlKA0KICAgIHJhZGl1c192YWwgPSBzY2FsZXM6OnJlc2NhbGUocmVnaXN0cm9zLCB0byA9IGMoMywgMTIpKQ0KICApDQpleHQgPC0gZXh0ICU+JQ0KICBsZWZ0X2pvaW4oDQogICAgc3RhdGlvbnMgJT4lIHNlbGVjdChucm8sIHJlZ2lzdHJvcywgcmFkaXVzX3ZhbCksDQogICAgYnkgPSAibnJvIg0KICApDQoNCiMgLS0tIDMuIENBUkdBUiBHRU9KU09OIFkgQUdSRUdBUiBEQVRPUyAoTlVFVk8pIC0tLQ0KDQojIDMuMSBGdW5jaW9uZXMgZGUgYXl1ZGEgKENPUlJFR0lEQSkNCm5vcm1hbGl6ZV9uYW1lIDwtIGZ1bmN0aW9uKG5hbWVfdmVjdG9yKSB7DQogIA0KICAjIDEuIE5vcm1hbGl6YWNpw7NuIChzaW4gYWNlbnRvcywgbWF5w7pzY3VsYXMpDQogIGtleSA8LSB0b3VwcGVyKGljb252KG5hbWVfdmVjdG9yLCBmcm9tID0gIlVURi04IiwgdG8gPSAiQVNDSUkvL1RSQU5TTElUIikpDQogIA0KICAjIDIuIExpbXBpZXphIChhcGxpY2FyIGNhc2Vfd2hlbiBhbCB2ZWN0b3IgJ2tleScpDQogIGRwbHlyOjpjYXNlX3doZW4oDQogICAga2V5ID09ICJDQVBJVEFMIEZFREVSQUwiIH4gIkNBQkEiLA0KICAgIGtleSA9PSAiQ0lVREFEIEFVVE9OT01BIERFIEJVRU5PUyBBSVJFUyIgfiAiQ0FCQSIsDQogICAga2V5ID09ICJUSUVSUkEgREVMIEZVRUdPLCBBTlRBUlRJREEgRSBJU0xBUyBERUwgQVRMQU5USUNPIFNVUiIgfiAiVElFUlJBIERFTCBGVUVHTyIsDQogICAgIyAoTWFudGVuZW1vcyBlbCB2YWxvciBwb3IgZGVmZWN0byAna2V5JykNCiAgICBUUlVFIH4ga2V5IA0KICApDQp9DQoNCiMgMy4yIENhcmdhciB5IHByZXBhcmFyIGVsIEdlb0pTT04gKEFob3JhIGZ1bmNpb25hcsOhKQ0KcHJvdmluY2lhc19zZiA8LSBwcm92aW5jaWFzX2dlbyAlPiUNCiAgbXV0YXRlKHByb3ZpbmNpYV9qb2luX2tleSA9IG5vcm1hbGl6ZV9uYW1lKG5vbWJyZSkpDQoNCiMgMy4zIEFncmVnYXIgZGF0b3MgZGUgRVNUQUNJT05FUyBhIG5pdmVsIFBST1ZJTkNJQQ0KcHJvdl90ZW1wIDwtIHN0YXRpb25zICU+JQ0KICBtdXRhdGUocHJvdmluY2lhX2pvaW5fa2V5ID0gbm9ybWFsaXplX25hbWUocHJvdmluY2lhKSkgJT4lDQogIGdyb3VwX2J5KHByb3ZpbmNpYV9qb2luX2tleSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICB0ZW1wX3Byb3YgPSBtZWFuKHRlbXBfbWVhbiwgbmEucm0gPSBUUlVFKSwNCiAgICByZWdpc3Ryb3NfcHJvdiA9IHN1bShyZWdpc3Ryb3MsIG5hLnJtID0gVFJVRSkNCiAgKQ0KDQojIDMuNCBBZ3JlZ2FyIGRhdG9zIGRlIEFNUExJVFVEIGEgbml2ZWwgUFJPVklOQ0lBDQpwcm92X2FtcCA8LSBleHQgJT4lDQogIG11dGF0ZShwcm92aW5jaWFfam9pbl9rZXkgPSBub3JtYWxpemVfbmFtZShwcm92aW5jaWEpKSAlPiUNCiAgZ3JvdXBfYnkocHJvdmluY2lhX2pvaW5fa2V5KSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGFtcF9wcm92ID0gbWVhbihhbXAsIG5hLnJtID0gVFJVRSkNCiAgKQ0KDQojIDMuNSBVbmlyIGRhdG9zIGFncmVnYWRvcyBhbCBHZW9KU09ODQptYXBfZGZfdGVtcCA8LSBwcm92aW5jaWFzX3NmICU+JQ0KICBsZWZ0X2pvaW4ocHJvdl90ZW1wLCBieSA9ICJwcm92aW5jaWFfam9pbl9rZXkiKQ0KICANCm1hcF9kZl9hbXAgPC0gcHJvdmluY2lhc19zZiAlPiUNCiAgbGVmdF9qb2luKHByb3ZfYW1wLCBieSA9ICJwcm92aW5jaWFfam9pbl9rZXkiKQ0KDQojIC0tLS0gNC4gUEFMRVRBUyBZIFTDjVRVTE8gKFR1IGPDs2RpZ28gb3JpZ2luYWwpIC0tLS0NCnBhbF9tZWFuIDwtIGNvbG9yTnVtZXJpYygNCiAgcGFsZXR0ZSA9IGMoIiMwMDAwRkYiLCAiI0ZGRkZGRiIsICIjRkYwMDAwIiksDQogIGRvbWFpbiA9IHN0YXRpb25zJHRlbXBfbWVhbiwgDQogIG5hLmNvbG9yID0gTkENCikNCnBhbF9hbXAgIDwtIGNvbG9yTnVtZXJpYyh2aXJpZGlzTGl0ZTo6bWFnbWEoMjU2KSwgZG9tYWluID0gZXh0JGFtcCwgbmEuY29sb3IgPSBOQSkNCg0KdGl0bGVfaHRtbCA8LSAnPGRpdiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjogcmdiYSgyNTUsMjU1LDI1NSwwLjgpOyANCiAgICAgICAgICAgICAgICAgICAgICBib3JkZXI6IDFweCBzb2xpZCBncmV5OyBib3JkZXItcmFkaXVzOiA1cHg7IA0KICAgICAgICAgICAgICAgICAgICAgIHBhZGRpbmc6IDVweCAxMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij4NCiAgICAgICAgICAgICAgPGg0IHN0eWxlPSJtYXJnaW46MDsiPkFuw6FsaXNpcyBkZSBFc3RhY2lvbmVzIFNNTjwvaDQ+DQogICAgICAgICAgICAgIDxzcGFuIHN0eWxlPSJmb250LXNpemU6IDEycHg7Ij5UZW1wLiBNZWRpYSB2cy4gQW1wbGl0dWQgVMOpcm1pY2E8L3NwYW4+DQogICAgICAgICAgICAgIDwvZGl2PicNCg0KIyAtLS0tIDUuIENSRUFDScOTTiBERUwgTUFQQSAoTW9kaWZpY2FkbykgLS0tLQ0KbV9tYXAgPC0gbGVhZmxldCgpICU+JSANCiAgYWRkVGlsZXMoKSAlPiUNCiAgYWRkQ29udHJvbChodG1sID0gdGl0bGVfaHRtbCwgcG9zaXRpb24gPSAidG9wbGVmdCIpICU+JQ0KICANCiAgIyAtLS0gQ2FwYSAxOiBFc3RhY2lvbmVzIChUZW1wIG1lZGlhKSAtLS0NCiAgYWRkQ2lyY2xlTWFya2VycygNCiAgICBkYXRhID0gc3RhdGlvbnMsDQogICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgDQogICAgZ3JvdXAgPSAiRXN0YWNpb25lcyAoVGVtcCBtZWRpYSkiLCAjIDwtLSBHcnVwbyAxDQogICAgcmFkaXVzID0gfnJhZGl1c192YWwsDQogICAgY29sb3IgPSB+cGFsX21lYW4odGVtcF9tZWFuKSwgZmlsbENvbG9yID0gfnBhbF9tZWFuKHRlbXBfbWVhbiksDQogICAgZmlsbE9wYWNpdHkgPSAwLjg1LCBzdHJva2UgPSBGQUxTRSwNCiAgICBwb3B1cCA9IH5wYXN0ZTAoIjxiPiIsIG5vbWJyZSwgIjwvYj48YnI+IiwgcHJvdmluY2lhLA0KICAgICAgICAgICAgICAgICAgICAiPGJyPlJlZ2lzdHJvczogIiwgcmVnaXN0cm9zLA0KICAgICAgICAgICAgICAgICAgICAiPGJyPlRlbXAgbWVkaWE6ICIsIHJvdW5kKHRlbXBfbWVhbiwyKSwgIiDCsEMiLA0KICAgICAgICAgICAgICAgICAgICAiPGJyPlNEOiAiLCByb3VuZChzZF90ZW1wLDEpLCAiIMKwQyIpDQogICkgJT4lDQogIGFkZExlZ2VuZChwb3NpdGlvbj0iYm90dG9tcmlnaHQiLCBwYWw9cGFsX21lYW4sIHZhbHVlcz1zdGF0aW9ucyR0ZW1wX21lYW4sDQogICAgICAgICAgICB0aXRsZT0iVGVtcCBtZWRpYSAowrBDKSIsIGdyb3VwPSJFc3RhY2lvbmVzIChUZW1wIG1lZGlhKSIpICU+JQ0KICANCiAgIyAtLS0gQ2FwYSAyOiBFc3RhY2lvbmVzIChBbXBsaXR1ZCkgLS0tDQogIGFkZENpcmNsZU1hcmtlcnMoDQogICAgZGF0YSA9IGV4dCwNCiAgICBsbmcgPSB+bG9uZ2l0dWQsIGxhdCA9IH5sYXRpdHVkLCANCiAgICBncm91cCA9ICJFc3RhY2lvbmVzIChBbXBsaXR1ZCkiLCAjIDwtLSBHcnVwbyAyDQogICAgcmFkaXVzID0gfnJhZGl1c192YWwsDQogICAgY29sb3IgPSB+cGFsX2FtcChhbXApLCBmaWxsQ29sb3IgPSB+cGFsX2FtcChhbXApLA0KICAgIGZpbGxPcGFjaXR5ID0gMC44NSwgc3Ryb2tlID0gRkFMU0UsDQogICAgcG9wdXAgPSB+cGFzdGUwKCI8Yj4iLCBub21icmUsICI8L2I+PGJyPiIsIHByb3ZpbmNpYSwNCiAgICAgICAgICAgICAgICAgICAgIjxicj5SZWdpc3Ryb3M6ICIsIHJlZ2lzdHJvcywNCiAgICAgICAgICAgICAgICAgICAgIjxicj5BbXBsaXR1ZCB0w6lybWljYSAocDkwLXAxMCk6ICIsIHJvdW5kKGFtcCwxKSwgIiDCsEMiKQ0KICApICU+JQ0KICBhZGRMZWdlbmQocG9zaXRpb249ImJvdHRvbWxlZnQiLCBwYWw9cGFsX2FtcCwgdmFsdWVzPWV4dCRhbXAsDQogICAgICAgICAgICB0aXRsZT0iQW1wbGl0dWQgKMKwQykiLCBncm91cD0iRXN0YWNpb25lcyAoQW1wbGl0dWQpIikgJT4lDQoNCiAgIyAtLS0gQ2FwYSAzOiBQcm92aW5jaWFzIChUZW1wIG1lZGlhKSAoTlVFVk8pIC0tLQ0KICBhZGRQb2x5Z29ucygNCiAgICBkYXRhID0gbWFwX2RmX3RlbXAsDQogICAgZ3JvdXAgPSAiUHJvdmluY2lhcyAoVGVtcCBtZWRpYSkiLCAjIDwtLSBHcnVwbyAzDQogICAgZmlsbENvbG9yID0gfnBhbF9tZWFuKHRlbXBfcHJvdiksDQogICAgd2VpZ2h0ID0gMSwgb3BhY2l0eSA9IDEsIGNvbG9yID0gIndoaXRlIiwgZmlsbE9wYWNpdHkgPSAwLjcsDQogICAgaGlnaGxpZ2h0T3B0aW9ucyA9IGhpZ2hsaWdodE9wdGlvbnMod2VpZ2h0ID0gMywgY29sb3IgPSAiIzY2NiIsIGZpbGxPcGFjaXR5ID0gMC45LCBicmluZ1RvRnJvbnQgPSBUUlVFKSwNCiAgICBsYWJlbCA9IH5zcHJpbnRmKCI8Yj4lczwvYj48YnIvPlRlbXAgbWVkaWEgcHJvdi46ICVzIMKwQyIsIA0KICAgICAgICAgICAgICAgICAgICAgbm9tYnJlLCByb3VuZCh0ZW1wX3Byb3YsIDEpKQ0KICApICU+JQ0KICBhZGRMZWdlbmQocG9zaXRpb249ImJvdHRvbXJpZ2h0IiwgcGFsPXBhbF9tZWFuLCB2YWx1ZXM9c3RhdGlvbnMkdGVtcF9tZWFuLCAjIFJldXNhIGxhIHBhbGV0YQ0KICAgICAgICAgICAgdGl0bGU9IlRlbXAgbWVkaWEgcHJvdi4gKMKwQykiLCBncm91cD0iUHJvdmluY2lhcyAoVGVtcCBtZWRpYSkiKSAlPiUNCg0KICAjIC0tLSBDYXBhIDQ6IFByb3ZpbmNpYXMgKEFtcGxpdHVkKSAoTlVFVk8pIC0tLQ0KICBhZGRQb2x5Z29ucygNCiAgICBkYXRhID0gbWFwX2RmX2FtcCwNCiAgICBncm91cCA9ICJQcm92aW5jaWFzIChBbXBsaXR1ZCkiLCAjIDwtLSBHcnVwbyA0DQogICAgZmlsbENvbG9yID0gfnBhbF9hbXAoYW1wX3Byb3YpLA0KICAgIHdlaWdodCA9IDEsIG9wYWNpdHkgPSAxLCBjb2xvciA9ICJ3aGl0ZSIsIGZpbGxPcGFjaXR5ID0gMC43LA0KICAgIGhpZ2hsaWdodE9wdGlvbnMgPSBoaWdobGlnaHRPcHRpb25zKHdlaWdodCA9IDMsIGNvbG9yID0gIiM2NjYiLCBmaWxsT3BhY2l0eSA9IDAuOSwgYnJpbmdUb0Zyb250ID0gVFJVRSksDQogICAgbGFiZWwgPSB+c3ByaW50ZigiPGI+JXM8L2I+PGJyLz5BbXBsaXR1ZCBwcm92LjogJXMgwrBDIiwgDQogICAgICAgICAgICAgICAgICAgICBub21icmUsIHJvdW5kKGFtcF9wcm92LCAxKSkNCiAgKSAlPiUNCiAgYWRkTGVnZW5kKHBvc2l0aW9uPSJib3R0b21sZWZ0IiwgcGFsPXBhbF9hbXAsIHZhbHVlcz1leHQkYW1wLCAjIFJldXNhIGxhIHBhbGV0YQ0KICAgICAgICAgICAgdGl0bGU9IkFtcGxpdHVkIHByb3YuICjCsEMpIiwgZ3JvdXA9IlByb3ZpbmNpYXMgKEFtcGxpdHVkKSIpICU+JQ0KDQogICMgLS0tIENvbnRyb2wgZGUgY2FwYXMgKEFob3JhIGNvbiA0IGdydXBvcykgLS0tDQogIGFkZExheWVyc0NvbnRyb2woDQogICAgYmFzZUdyb3VwcyA9IGMoIkVzdGFjaW9uZXMgKFRlbXAgbWVkaWEpIiwgIlByb3ZpbmNpYXMgKFRlbXAgbWVkaWEpIiwgDQogICAgICAgICAgICAgICAgICAgIkVzdGFjaW9uZXMgKEFtcGxpdHVkKSIsICJQcm92aW5jaWFzIChBbXBsaXR1ZCkiKSwgDQogICAgb3B0aW9ucyA9IGxheWVyc0NvbnRyb2xPcHRpb25zKGNvbGxhcHNlZCA9IEZBTFNFLCBhdXRvWkluZGV4ID0gVFJVRSkNCiAgKQ0KICANCiMgLS0tLSA2LiBNT1NUUkFSIEVMIE1BUEEgLS0tLQ0KbV9tYXANCmBgYA0KDQojIDcuIEFuw6FsaXNpcyBQcm9mdW5kbyBkZSBTZXJpZXMgVGVtcG9yYWxlcw0KDQpWb2x2ZW1vcyBhIG51ZXN0cm8gZGF0YWZyYW1lICJqdXN0byIgKG1vbnRobHlfbmF0aW9uYWxfZmFpcikgcGFyYSB1biBhbsOhbGlzaXMgbcOhcyBwcm9mdW5kbyBkZSBsYXMgdGVuZGVuY2lhcyBhIGxvIGxhcmdvIGRlbCB0aWVtcG8uDQoNClVuIGhpc3RvZ3JhbWEgZGUgbGFzIHRlbXBlcmF0dXJhcyBob3JhcmlhcyAodXNhbmRvIHVuYSBtdWVzdHJhKSBub3MgcGVybWl0ZSBhbm90YXIgbGEgbWVkaWEgeSBtZWRpYW5hIGV4YWN0YXMuDQoNCmBgYHtyfQ0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT0gVklTVUFMRVMgPT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMgLS0tLSBIaXN0b2dyYW1hIGRlIHRlbXBlcmF0dXJhIChtdWVzdHJhKSBjb24gbWVkaWEvbWVkaWFuYSB5IGzDrW1pdGVzIMO6dGlsZXMNCnNldC5zZWVkKDEyMykNCm11ICAgICAgIDwtIG1lYW4oZGYkdGVtcCk7IG1kIDwtIG1lZGlhbihkZiR0ZW1wKQ0KcF9oaXN0IDwtIHBsb3RfbHkoeCA9IHNhbXBsZShkZiR0ZW1wLCAyMDAwMDApLCB0eXBlID0gImhpc3RvZ3JhbSIsIG5iaW5zeCA9IDEwMCkgJT4lDQogIHBsb3RseTo6bGF5b3V0KA0KICAgIHRpdGxlID0gbGlzdCgNCiAgICAgIHRleHQgPSAiTGEgZGlzdHJpYnVjacOzbiBkZSB0ZW1wZXJhdHVyYXMgaG9yYXJpYXMgbXVlc3RyYSB1biBhbXBsaW8gcmFuZ28gZXN0YWNpb25hbDxicj48c3VwPk11ZXN0cmEgZGUgaGFzdGEgMjAwayByZWdpc3Ryb3M7IGzDrW5lYXMgcHVudGVhZGFzID0gbWVkaWEgeSBtZWRpYW5hPC9zdXA+IiwNCiAgICAgIHggPSAwLjA1LA0KICAgICAgeGFuY2hvciA9ICdsZWZ0Jw0KICAgICksDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlRlbXBlcmF0dXJhICjCsEMpIiksDQogICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIkZyZWN1ZW5jaWEgKGNvbnRlbyBkZSBob3JhcykiKSwNCiAgICBzaGFwZXMgPSBsaXN0KA0KICAgICAgbGlzdCh0eXBlPSJsaW5lIiwgeDA9bXUsIHgxPW11LCB5MD0wLCB5MT0xLCB4cmVmPSJ4IiwgeXJlZj0icGFwZXIiLCBsaW5lPWxpc3QoZGFzaD0iZGFzaCIpKSwNCiAgICAgIGxpc3QodHlwZT0ibGluZSIsIHgwPW1kLCB4MT1tZCwgeTA9MCwgeTE9MSwgeHJlZj0ieCIsIHlyZWY9InBhcGVyIiwgbGluZT1saXN0KGRhc2g9ImRvdCIpKQ0KICAgICksDQogICAgYW5ub3RhdGlvbnMgPSBsaXN0KA0KICAgICAgIyAtLS0gQW5vdGFjacOzbiBwYXJhIGxhIE1lZGlhIChtdSA9IDE2LjMpIC0tLQ0KICAgICAgbGlzdCgNCiAgICAgICAgeCA9IG11LCB5ID0gMC44OCwgICMgPC0tIFBvc2ljacOzbiBZDQogICAgICAgIHhyZWYgPSAieCIsIHlyZWYgPSAicGFwZXIiLA0KICAgICAgICB0ZXh0ID0gcGFzdGUwKCJNZWRpYTogIiwgcm91bmQobXUsMSksIsKwQyIpLA0KICAgICAgICB4YW5jaG9yID0gJ3JpZ2h0JywgIyA8LS0gTXVldmUgZWwgdGV4dG8gYSBsYSBpenF1aWVyZGEgZGVsIHB1bnRvDQogICAgICAgIHNob3dhcnJvdyA9IFRSVUUsIA0KICAgICAgICBheCA9IC00MCwgIyA8LS0gRmxlY2hhIGFwdW50YSBkZXNkZSBsYSBpenF1aWVyZGEgKC00MCkNCiAgICAgICAgYXkgPSAtNDAsDQogICAgICAgIGZvbnQgPSBsaXN0KHNpemUgPSAxMCkNCiAgICAgICksDQogICAgICAjIC0tLSBBbm90YWNpw7NuIHBhcmEgbGEgTWVkaWFuYSAobWQgPSAxNi44KSAtLS0NCiAgICAgIGxpc3QoDQogICAgICAgIHggPSBtZCwgeSA9IDAuODIsICMgPC0tIFBvc2ljacOzbiBZIChtw6FzIGJhamEpDQogICAgICAgIHhyZWYgPSAieCIsIHlyZWYgPSAicGFwZXIiLA0KICAgICAgICB0ZXh0ID0gcGFzdGUwKCJNZWRpYW5hOiAiLCByb3VuZChtZCwxKSwiwrBDIiksDQogICAgICAgIHhhbmNob3IgPSAnbGVmdCcsICMgPC0tIE11ZXZlIGVsIHRleHRvIGEgbGEgZGVyZWNoYSBkZWwgcHVudG8NCiAgICAgICAgc2hvd2Fycm93ID0gVFJVRSwgDQogICAgICAgIGF4ID0gNDAsICMgPC0tIEZsZWNoYSBhcHVudGEgZGVzZGUgbGEgZGVyZWNoYSAoNDApDQogICAgICAgIGF5ID0gLTMwLA0KICAgICAgICBmb250ID0gbGlzdChzaXplID0gMTApDQogICAgICApLA0KICAgICAgIyAtLS0gQW5vdGFjacOzbiBwYXJhIGxhIGZ1ZW50ZSAoZm9vdGVyKSAtLS0NCiAgICAgIGxpc3QodGV4dCA9ICJGdWVudGU6IFNlcnZpY2lvIE1ldGVvcm9sw7NnaWNvIE5hY2lvbmFsIChTTU4pIOKAkyBlbGFib3JhY2nDs24gcHJvcGlhLiIsDQogICAgICAgICAgIHNob3dhcnJvdyA9IEZBTFNFLCB4cmVmID0gInBhcGVyIiwgeXJlZiA9ICJwYXBlciIsIHggPSAxLCB5ID0gLTAuMTUsDQogICAgICAgICAgIHhhbmNob3IgPSAncmlnaHQnLCB5YW5jaG9yID0gJ2F1dG8nLCBmb250ID0gbGlzdChzaXplID0gMTApKQ0KICAgICksDQogICAgbWFyZ2luID0gbGlzdCh0ID0gMTAwLCBiID0gODApDQogICkNCg0KcF9oaXN0DQpgYGANCg0KVHJhemFtb3MgbGEgc2VyaWUgdGVtcG9yYWwgbmFjaW9uYWwgImp1c3RhIiB5IGxlIGFqdXN0YW1vcyB1bmEgdGVuZGVuY2lhIGxpbmVhbC4gRWwgZ3LDoWZpY28gbXVlc3RyYSB1biBjaWNsbyBlc3RhY2lvbmFsIG11eSBmdWVydGUuIEF1bnF1ZSBsYSBsw61uZWEgZGUgdGVuZGVuY2lhIHBhcmVjZSB0ZW5lciB1bmEgbGlnZXJhIHBlbmRpZW50ZSBuZWdhdGl2YSwgZWwgcC12YWxvciAocCA+IDAuMDUpIGVzIGFsdG8uIEVzdG8gaW5kaWNhIHF1ZSBsYSB0ZW5kZW5jaWEgb2JzZXJ2YWRhIG5vIGVzIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmEgeSBlcyBjb25zaXN0ZW50ZSBjb24gZWwgYXphci4gQSBlZmVjdG9zIHByw6FjdGljb3MsIGxhIHRlbmRlbmNpYSBlbiBlc3RlIHBlcsOtb2RvIGVzIHBsYW5hLg0KDQpgYGB7cn0NCiMgMS4gUHJlcGFyYXIgbGEgbMOtbmVhIGRlIHRlbmRlbmNpYSAoYWp1c3RhciBlbCBtb2RlbG8gbGluZWFsKQ0KZGZfdHJlbmQgPC0gbW9udGhseV9uYXRpb25hbF9mYWlyICU+JSANCiAgc2VsZWN0KHltLCB0ZW1wX21lYW4pDQoNCm1kbCA8LSBsbSh0ZW1wX21lYW4gfiBhcy5udW1lcmljKHltKSwgZGF0YSA9IGRmX3RyZW5kKQ0KDQojIDIuIENhbGN1bGFyIGxvcyB2YWxvcmVzIHByZWRpY2hvcw0KZGZfdHJlbmQgPC0gZGZfdHJlbmQgJT4lIA0KICBtdXRhdGUocHJlZGljdGVkID0gcHJlZGljdChtZGwpKQ0KDQojIC0tLSBJTklDSU8gREUgTEEgQ09SUkVDQ0nDk04gLS0tDQojIDIuMS4gRXh0cmFlciBsb3MgcmVzdWx0YWRvcyBkZWwgbW9kZWxvDQptb2RlbF9zdW1tYXJ5IDwtIHN1bW1hcnkobWRsKQ0KDQojIDIuMi4gT2J0ZW5lciBsYSBwZW5kaWVudGUgKHNsb3BlKSB5IGVsIHAtdmFsb3IgKHB2KQ0KIyBFbCBjb2VmaWNpZW50ZSBbMiwxXSBlcyBsYSBwZW5kaWVudGUgKGVzdGltYWNpw7NuIGRlICdhcy5udW1lcmljKHltKScpDQojIEVsIHByZWRpY3RvciAnYXMubnVtZXJpYyh5bSknIGN1ZW50YSBlbiBkw61hcywgYXPDrSBxdWUgY29udmVydGltb3MgbGEgcGVuZGllbnRlIGEgZMOpY2FkYXMNCnNsb3BlX2RhaWx5IDwtIG1vZGVsX3N1bW1hcnkkY29lZmZpY2llbnRzWzIsIDFdDQpzbG9wZV9kZWNhZGUgPC0gc2xvcGVfZGFpbHkgKiAzNjUuMjUgKiAxMCAjIENvbnZlcnRpciBkZSBkw61hcyBhIGTDqWNhZGFzDQoNCiMgRWwgY29lZmljaWVudGUgWzIsNF0gZXMgZWwgcC12YWxvciBkZSBsYSBwZW5kaWVudGUNCnB2IDwtIG1vZGVsX3N1bW1hcnkkY29lZmZpY2llbnRzWzIsIDRdDQoNCnBfdHJlbmQgPC0gcGxvdF9seShkZl90cmVuZCwgeCA9IH55bSkgJT4lDQogIA0KICAjIENhcGEgMTogRGF0b3Mgb2JzZXJ2YWRvcyAoU2VyaWUgZGUgdGllbXBvKQ0KICBhZGRfdHJhY2UoeSA9IH50ZW1wX21lYW4sIG5hbWUgPSAnVGVtcGVyYXR1cmEgT2JzZXJ2YWRhIChNZWRpYSknLCANCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMrbWFya2VycycsIA0KICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAnIzFmNzdiNCcpLCBtYXJrZXIgPSBsaXN0KHNpemUgPSA0KSkgJT4lDQogIA0KICAjIENhcGEgMjogTMOtbmVhIGRlIFRlbmRlbmNpYSAoTW9kZWxvIExpbmVhbCkNCiAgYWRkX3RyYWNlKHkgPSB+cHJlZGljdGVkLCBuYW1lID0gJ1RlbmRlbmNpYSBMaW5lYWwnLCANCiAgICAgICAgICAgIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnLCANCiAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJyNkNjI3MjgnLCBkYXNoID0gJ2Rhc2gnKSwgDQogICAgICAgICAgICBzaG93bGVnZW5kID0gVFJVRSkgJT4lDQogIA0KICAjIC0tLSBMYXlvdXQgRGVmaW5pdGl2byAoVMOtdHVsb3MgQ29tYmluYWRvcykgLS0tDQogIGxheW91dCgNCiAgICANCiAgICAjIENvbWJpbmFtb3MgVMOtdHVsbyB5IFN1YnTDrXR1bG8gZW4gdW4gc29sbyBibG9xdWUNCiAgICB0aXRsZSA9IGxpc3QoDQogICAgICB0ZXh0ID0gcGFzdGUwKA0KICAgICAgICAjIFTDrXR1bG8gKE5lZ3JpdGEsIFRhbWHDsW8gMTgpDQogICAgICAgICI8Yj5GdWVydGUgQ2ljbG8gRXN0YWNpb25hbCBTaW4gVGVuZGVuY2lhIExpbmVhbCBTaWduaWZpY2F0aXZhPC9iPiIsDQogICAgICAgIA0KICAgICAgICAjIFNhbHRvIGRlIGzDrW5lYQ0KICAgICAgICAiPGJyPiIsDQogICAgICAgIA0KICAgICAgICAjIFN1YnTDrXR1bG8gKFRhbWHDsW8gMTIsIENvbG9yIGdyaXMpDQogICAgICAgICI8c3BhbiBzdHlsZT0nZm9udC1zaXplOiAxMnB4OyBjb2xvcjogZ3JheTsnPiIsIA0KICAgICAgICAiUGVuZGllbnRlIGNhbGN1bGFkYTogIiwgDQogICAgICAgIHNwcmludGYoIiUuM2YgwrBDL2TDqWNhZGEgKHAtdmFsb3I6ICUuMmYpIiwgc2xvcGVfZGVjYWRlLCBwdiksDQogICAgICAgICIuIEVsIHAtdmFsb3IgYWx0byAocCA+IDAuMDUpIGluZGljYSBxdWUgbGEgdGVuZGVuY2lhIGVzIG51bGEuIiwNCiAgICAgICAgIjwvc3Bhbj4iDQogICAgICApLA0KICAgICAgDQogICAgICAjIC0tLSBQYXLDoW1ldHJvcyBkZSBDZW50cmFkbyAtLS0NCiAgICAgIHggPSAwLjUsICAgICAgICAgICAgIyBQb3NpY2nDs24gaG9yaXpvbnRhbCAoMC41ID0gY2VudHJvKQ0KICAgICAgeGFuY2hvciA9ICdjZW50ZXInLCAgICMgQW5jbGFqZSAocXVlIGVsIGNlbnRybyBkZWwgdGV4dG8gZXN0w6kgZW4geD0wLjUpDQogICAgICB5ID0gMC45NSwgICAgICAgICAgICMgUG9zaWNpw7NuIHZlcnRpY2FsIChjZXJjYSBkZSBsYSBwYXJ0ZSBzdXBlcmlvcikNCiAgICAgIHlhbmNob3IgPSAndG9wJw0KICAgICksDQogICAgDQogICAgIyBZYSBubyB1c2Ftb3MgJ2Fubm90YXRpb25zJyBwYXJhIGVsIHN1YnTDrXR1bG8NCiAgICANCiAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiRmVjaGEiLCBkdGljayA9ICJNMTIiLCB0aWNrZm9ybWF0ID0gIiViICVZIiksDQogICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlRlbXBlcmF0dXJhIG1lZGlhICjCsEMpIiksDQogICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9IGxpc3QodGV4dD0nPGI+RGF0b3M8L2I+JykpLA0KICAgIA0KICAgICMgTWFyZ2VuIHBhcmEgZGFyIGVzcGFjaW8NCiAgICBtYXJnaW4gPSBsaXN0KHQgPSAxMjApIA0KICApDQoNCnBfdHJlbmQNCmBgYA0KDQpFc3RlIHRpcG8gZGUgZXN0dWRpb3Mgc29icmUgbGEgY2xpbWF0b2xvZ8OtYSB5IHN1cyBhbm9tYWzDrWFzIHN1ZWxlbiBzZXIgY29uIGRhdG9zIG11Y2hvIG3DoXMgZXh0ZW5zb3MsIGRlIDMwIGHDsW9zIGFsIG1lbm9zLiBBY8OhIGNvbnRhbW9zIHNvbG8gY29uIDUgY29tcGxldG9zICgyMDE4LTIwMjMpLg0KDQpDb24gZXNhIGNsaW1hdG9sb2fDrWEgYmFzZSAoMjAxOC0yMDIzKSwgY2FsY3VsYW1vcyBsYXMgYW5vbWFsw61hcyBtZW5zdWFsZXMgKGxhIGRpZmVyZW5jaWEgZGUgY2FkYSBtZXMgY29uIHN1IHByb21lZGlvIGhpc3TDs3JpY28pLiBFbCBncsOhZmljbyBkZSBiYXJyYXMgcmVzdWx0YW50ZSByZXNhbHRhIGxvcyBwZXLDrW9kb3MgY8OhbGlkb3MgcHJvbG9uZ2Fkb3MgZW4gMjAyMyB5IHVuIG5vdGFibGUgZXZlbnRvIGZyw61vIGVuIDIwMjQuDQoNCmBgYHtyfQ0KIyA9PT09PT09PT09PT09PT09PT0gUHJvbWVkaW8gZGlhcmlvIG5hY2lvbmFsIChwbG90bHkpID09PT09PT09PT09PT09PT09PQ0KDQojIC0tLS0tLS0tLSBQYXLDoW1ldHJvcyAtLS0tLS0tLS0NCnZhbHVlX2NvbCAgPC0gaWYgKCJ0bWF4IiAlaW4lIG5hbWVzKGRhaWx5X3N0YXRpb24pKSAidG1heCIgZWxzZSAidGVtcF9tZWFuIg0KcGxvdF9zdGFydCA8LSBhcy5EYXRlKCIyMDE4LTAxLTAxIikNCnBsb3RfZW5kICAgPC0gYXMuRGF0ZSgiMjAyMy0xMi0zMSIpDQpub3JtX3llYXJzIDwtIDIwMTg6MjAyMw0KDQpzdG9waWZub3QoYWxsKGMoInltZCIsInllYXIiLCJtb250aCIsIHZhbHVlX2NvbCkgJWluJSBuYW1lcyhkYWlseV9zdGF0aW9uKSkpDQoNCiMgLS0tLS0tLS0tIDEpIFByb21lZGlvIGRpYXJpbyBuYWNpb25hbCAtLS0tLS0tLS0NCm5hdF9kYWlseSA8LSBkYWlseV9zdGF0aW9uICU+JQ0KICBmaWx0ZXIoeW1kID49IHBsb3Rfc3RhcnQsIHltZCA8PSBwbG90X2VuZCkgJT4lDQogIGdyb3VwX2J5KHltZCkgJT4lDQogIHN1bW1hcmlzZSh2YWx1ZSA9IG1lYW4oLmRhdGFbW3ZhbHVlX2NvbF1dLCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgbXV0YXRlKA0KICAgIHllYXIgID0geWVhcih5bWQpLA0KICAgIGRveSAgID0geWRheSh5bWQpLA0KICAgIGlzMjlmID0gKG1vbnRoKHltZCkgPT0gMiAmIG1kYXkoeW1kKSA9PSAyOSkNCiAgKSAlPiUNCiAgZmlsdGVyKCFpczI5ZikNCg0KIyAtLS0tLS0tLS0gMikgQ2xpbWF0b2xvZ8OtYSBkaWFyaWEgKHByb21lZGlvIHBvciBET1kpIC0tLS0tLS0tLQ0KY2xpbV9kb3kgPC0gbmF0X2RhaWx5ICU+JQ0KICBmaWx0ZXIoeWVhciAlaW4lIG5vcm1feWVhcnMpICU+JQ0KICBncm91cF9ieShkb3kpICU+JQ0KICBzdW1tYXJpc2UoY2xpbSA9IG1lYW4odmFsdWUsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAiZHJvcCIpDQoNCiMgU3Vhdml6YWRvIHRpcG8gU01OIChjdXJ2YSBuZWdyYSkNCmxvIDwtIGxvZXNzKGNsaW0gfiBkb3ksIGRhdGEgPSBjbGltX2RveSwgc3BhbiA9IDAuMjUsIGRlZ3JlZSA9IDIsIGZhbWlseSA9ICJnYXVzc2lhbiIpDQpjbGltX2RveSA8LSBjbGltX2RveSAlPiUNCiAgbXV0YXRlKGNsaW1fc21vb3RoID0gYXMubnVtZXJpYyhwcmVkaWN0KGxvLCBuZXdkYXRhID0gZGF0YS5mcmFtZShkb3kgPSBkb3kpKSkpDQoNCiMgLS0tLS0tLS0tIDMpIFVuaXIgc2VyaWUgZGlhcmlhIGNvbiBsYSBjbGltYXRvbG9nw61hIC0tLS0tLS0tLQ0KcGxvdF9kZiA8LSBuYXRfZGFpbHkgJT4lDQogIGxlZnRfam9pbihjbGltX2RveSwgYnkgPSAiZG95IikgJT4lDQogIG11dGF0ZSgNCiAgICBkZWx0YSA9IHZhbHVlIC0gY2xpbV9zbW9vdGgsDQogICAgc2lnbm8gPSBpZl9lbHNlKGRlbHRhID49IDAsICJzb2JyZSIsICJiYWpvIikNCiAgKQ0KDQp0aXR1bG9fdmFyIDwtIGlmICh2YWx1ZV9jb2wgPT0gInRtYXgiKSB7DQogICJUZW1wZXJhdHVyYSBtw6F4aW1hIOKAlCBQcm9tZWRpbyBkaWFyaW8gbmFjaW9uYWwiDQp9IGVsc2Ugew0KICAiVGVtcGVyYXR1cmEg4oCUIFByb21lZGlvIGRpYXJpbyBuYWNpb25hbCINCn0NCg0KeV9ybmcgPC0gcmFuZ2UoYyhwbG90X2RmJHZhbHVlLCBwbG90X2RmJGNsaW1fc21vb3RoKSwgbmEucm0gPSBUUlVFKSArIGMoLTEsIDEpDQoNCiMgLS0tLS0tLS0tIDQpIEdyw6FmaWNvIChwbG90bHkpIC0tLS0tLS0tLQ0KcF9hbm9tIDwtIHBsb3RfbHkocGxvdF9kZiwgeCA9IH55bWQsIGhvdmVyaW5mbyA9ICJza2lwIikgJT4lDQogICMgQmFycmFzIHZlcnRpY2FsZXMgKGNvbW8gbGluZXJhbmdlIGVuIGdncGxvdCkNCiAgYWRkX3NlZ21lbnRzKA0KICAgIHkgPSB+cG1pbih2YWx1ZSwgY2xpbV9zbW9vdGgpLA0KICAgIHllbmQgPSB+cG1heCh2YWx1ZSwgY2xpbV9zbW9vdGgpLA0KICAgIHhlbmQgPSB+eW1kLA0KICAgIGNvbG9yID0gfnNpZ25vLA0KICAgIGNvbG9ycyA9IGMoImJham8iID0gIiM1NUMzRDMiLCAic29icmUiID0gIiNDMjNCQUEiKSwNCiAgICBuYW1lID0gIkRpZmVyZW5jaWEiLA0KICAgIHNob3dsZWdlbmQgPSBGQUxTRQ0KICApICU+JQ0KICAjIFNlcmllIG9ic2VydmFkYQ0KICBhZGRfbGluZXMoDQogICAgeSA9IH52YWx1ZSwNCiAgICBuYW1lID0gIlByb21lZGlvIGRpYXJpbyBuYWNpb25hbCIsDQogICAgbGluZSA9IGxpc3QoY29sb3IgPSAicmdiYSgxNjAsMTYwLDE2MCwxKSIsIHdpZHRoID0gMSksDQogICAgaG92ZXJ0ZW1wbGF0ZSA9ICIle3h8JWQtJW0tJVl9PGJyPlByb21lZGlvOiAle3k6LjFmfcKwQzxleHRyYT48L2V4dHJhPiINCiAgKSAlPiUNCiAgIyDigJxOb3JtYWzigJ0gc3Vhdml6YWRhDQogIGFkZF9saW5lcygNCiAgICB5ID0gfmNsaW1fc21vb3RoLA0KICAgIG5hbWUgPSBzcHJpbnRmKCJOb3JtYWwgZGlhcmlhICVk4oCTJWQgKHN1YXZpemFkYSkiLCBtaW4obm9ybV95ZWFycyksIG1heChub3JtX3llYXJzKSksDQogICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCB3aWR0aCA9IDIpLA0KICAgIGhvdmVydGVtcGxhdGUgPSAiJXt4fCVkLSVtLSVZfTxicj5Ob3JtYWw6ICV7eTouMWZ9wrBDPGV4dHJhPjwvZXh0cmE+Ig0KICApICU+JQ0KICBsYXlvdXQoDQogICAgdGl0bGUgPSBsaXN0KA0KICAgICAgdGV4dCA9IHBhc3RlMCgNCiAgICAgICAgdGl0dWxvX3ZhciwNCiAgICAgICAgIjxicj48c3VwPkFyZ2VudGluYSDigJQgIiwgZm9ybWF0KHBsb3Rfc3RhcnQsICIlWS0lbS0lZCIpLA0KICAgICAgICAiIGEgIiwgZm9ybWF0KHBsb3RfZW5kLCAiJVktJW0tJWQiKSwgIjwvc3VwPiINCiAgICAgICkNCiAgICApLA0KICAgIHhheGlzID0gbGlzdCh0aXRsZSA9IE5VTEwsIGR0aWNrID0gIk0zIiwgdGlja2Zvcm1hdCA9ICIlYiAlWSIpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJUZW1wZXJhdHVyYSAowrBDKSIsIHJhbmdlID0geV9ybmcpLA0KICAgIG1hcmdpbiA9IGxpc3QoYiA9IDgwKSwNCiAgICBhbm5vdGF0aW9ucyA9IGxpc3QoDQogICAgICBsaXN0KA0KICAgICAgICB0ZXh0ID0gc3ByaW50ZigNCiAgICAgICAgICAiTm9ybWFsIGRpYXJpYTogcHJvbWVkaW8gJWTigJMlZCAobG9lc3MpLiBGdWVudGU6IFNNTiDigJMgZWxhYm9yYWNpw7NuIHByb3BpYS4iLA0KICAgICAgICAgIG1pbihub3JtX3llYXJzKSwgbWF4KG5vcm1feWVhcnMpDQogICAgICAgICksDQogICAgICAgIHNob3dhcnJvdyA9IEZBTFNFLCB4cmVmID0gInBhcGVyIiwgeXJlZiA9ICJwYXBlciIsDQogICAgICAgIHggPSAxLCB5ID0gLTAuMTgsIHhhbmNob3IgPSAicmlnaHQiLCBmb250ID0gbGlzdChzaXplID0gMTApDQogICAgICApDQogICAgKQ0KICApDQoNCnBfYW5vbQ0KIyA9PT09PT09PT09PT09PT09PT0gRklOID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KYGBgDQoNCkNvbXBhcmFtb3MgZWwgY2ljbG8gZGl1cm5vIChob3JhcmlvKSBwcm9tZWRpbyBwYXJhIGVsIHZlcmFubyB5IGVsIGludmllcm5vLiBFbCBncsOhZmljbyBtdWVzdHJhIGNsYXJhbWVudGUgbGEgZGlmZXJlbmNpYSBlbiBsYSBhbXBsaXR1ZCB0w6lybWljYSBkaWFyaWEgeSBsYSBkaWZlcmVuY2lhIGRlIH4xMsKwQyBlbnRyZSBsYXMgZXN0YWNpb25lcy4NCmBgYHtyfQ0KZGl1cm5hbCA8LSBkZiAlPiUNCiAgZmlsdGVyKCFpcy5uYSh0ZW1wKSkgJT4lDQogIGdyb3VwX2J5KGhvcmEsIG0gPSBtb250aChkYXRldGltZSkpICU+JQ0KICBzdW1tYXJpc2UodGVtcF9tZWFuID0gbWVhbih0ZW1wLCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXBzID0gJ2Ryb3AnKSAlPiUNCiAgbXV0YXRlKA0KICAgIHNlYXNvbiA9IGNhc2Vfd2hlbigNCiAgICAgIG0gJWluJSBjKDEyLCAxLCAyKSB+ICJWZXJhbm8gKERKRikiLA0KICAgICAgbSAlaW4lIGMoNiwgNywgOCkgIH4gIkludmllcm5vIChKSkEpIiwNCiAgICAgIC5kZWZhdWx0ID0gTkFfY2hhcmFjdGVyXyAjIEFzaWduYSBOQSBhbCByZXN0byAob3Rvw7FvL3ByaW1hdmVyYSkNCiAgICApDQogICkgJT4lDQogIGZpbHRlcighaXMubmEoc2Vhc29uKSkgJT4lDQogIGdyb3VwX2J5KGhvcmEsIHNlYXNvbikgJT4lDQogIHN1bW1hcmlzZSh0ZW1wX21lYW4gPSBtZWFuKHRlbXBfbWVhbiwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lDQogICMgQ29udmVydGlyICdzZWFzb24nIGEgZmFjdG9yIHBhcmEgb3JkZW5hciBlbiBncsOhZmljb3MNCiAgbXV0YXRlKA0KICAgIHNlYXNvbiA9IGZhY3RvcihzZWFzb24sIGxldmVscyA9IGMoIlZlcmFubyAoREpGKSIsICJJbnZpZXJubyAoSkpBKSIpKQ0KICApDQoNCnBfZGl1cm5hbCA8LSBwbG90X2x5KGRpdXJuYWwsIHg9fmhvcmEsIHk9fnRlbXBfbWVhbiwgY29sb3I9fnNlYXNvbiwgdHlwZT0nc2NhdHRlcicsIG1vZGU9J2xpbmVzK21hcmtlcnMnLA0KICAgICAgICAgICAgICAgICAgICAgY29sb3JzID0gYygiSW52aWVybm8gKEpKQSkiID0gIiMxZjc3YjQiLCAiVmVyYW5vIChESkYpIiA9ICIjZDYyNzI4IikpICU+JQ0KICBsYXlvdXQoDQogICAgdGl0bGUgPSBsaXN0KHRleHQgPSAiVGVtcGVyYXR1cmFzIG1lZGlhcyBkaXVybmFzOiBWZXJhbm8gZXMgfjEywrBDIG3DoXMgY8OhbGlkbyBxdWUgZWwgaW52aWVybm88YnI+PHN1cD5Qcm9tZWRpbyBob3JhcmlvIG5hY2lvbmFsIHBhcmEgVmVyYW5vIChESkYpIGUgSW52aWVybm8gKEpKQSkgZGVsIHBlcsOtb2RvIDIwMTgtMjAyNDwvc3VwPiIpLA0KICAgIA0KICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJIb3JhIGxvY2FsIiwgbnRpY2tzID0gMjQpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJUZW1wZXJhdHVyYSBtZWRpYSAowrBDKSIpLA0KICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSBsaXN0KHRleHQgPSAnPGI+IEVzdGFjacOzbiA8L2I+JykpLA0KICAgIGFubm90YXRpb25zID0gbGlzdCgNCiAgICAgIGxpc3QodGV4dCA9ICJGdWVudGU6IFNNTiDigJMgZWxhYm9yYWNpw7NuIHByb3BpYS4iLA0KICAgICAgICAgICBzaG93YXJyb3cgPSBGQUxTRSwgeHJlZiA9ICJwYXBlciIsIHlyZWYgPSAicGFwZXIiLCB4ID0gMSwgeSA9IC0wLjE1LA0KICAgICAgICAgICB4YW5jaG9yID0gJ3JpZ2h0JywgeWFuY2hvciA9ICdhdXRvJywgZm9udCA9IGxpc3Qoc2l6ZSA9IDEwKSkNCiAgICApLA0KICAgIG1hcmdpbiA9IGxpc3QoYiA9IDgwKQ0KICApDQoNCnBfZGl1cm5hbA0KYGBgDQoNCiMgOC4gQW7DoWxpc2lzIERldGFsbGFkb3MgcG9yIFZhcmlhYmxlDQojIyA4LjEuIFByZWNpcGl0YWNpw7NuDQpVdGlsaXphbmRvIGVsIGNvbmp1bnRvIGRlIGRhdG9zIGRlIHByZWNpcGl0YWNpw7NuIG3DoXMgbGFyZ28gKDE5OTEtMjAyNCksIGFuYWxpemFtb3MgbGEgY29udHJpYnVjacOzbiBkZSBsb3MgZXZlbnRvcyBkZSBsbHV2aWEgImZ1ZXJ0ZSIuDQpDcmVhbW9zIHVuIGdyw6FmaWNvIGRlIGJhcnJhcyBhcGlsYWRhcyBxdWUgbXVlc3RyYSBsYSBwcmVjaXBpdGFjacOzbiBtZW5zdWFsIHRvdGFsIChwcm9tZWRpbyBuYWNpb25hbCkgZGl2aWRpZGEgZW4gZG9zIGNvbXBvbmVudGVzOg0KLSBBenVsOiBMbHV2aWEgcHJvdmVuaWVudGUgZGUgZMOtYXMgImxpZ2Vyb3MiICg8IDUwIG1tKS4NCi0gUm9qbzogTGx1dmlhIHByb3ZlbmllbnRlIGRlIGTDrWFzICJmdWVydGVzIiAo4omlIDUwIG1tKS4NCkVzdG8gdmlzdWFsaXphIGVsIGltcGFjdG8gc2lnbmlmaWNhdGl2byBxdWUgdGllbmVuIGxvcyBldmVudG9zIGV4dHJlbW9zIGVuIGVsIHRvdGFsIGRlIHByZWNpcGl0YWNpw7NuIG1lbnN1YWwuDQoNCmBgYHtyfQ0KIyA9PT09PT09PT09PT09IEFOw4FMSVNJUyBERSBQUkVDSVBJVEFDScOTTiAoMTk5MS0yMDI0KSA9PT09PT09PT09PT0NCg0KIyAxLiBQcmVwYXJhY2nDs24gZGUgRGF0b3M6IENvbnZlcnRpciB5IGHDsWFkaXIgYmFuZGVyYSBkZSBkw61hIGZ1ZXJ0ZQ0KcmFpbl9kYWlseV9mdWxsIDwtIGRmX2xsdXZpYV9jb25faW5mbyB8Pg0KICBtdXRhdGUoDQogICAgRkVDSEEgPSBhcy5EYXRlKGZlY2hhKSwNCiAgICBwcCA9IGFzLm51bWVyaWMocHJlY2lwaXRhY2lvbikNCiAgKSB8Pg0KICBmaWx0ZXIoIWlzLm5hKG5ybyksICFpcy5uYShmZWNoYSksICFpcy5uYShwcCkpIHw+DQogIHNlbGVjdChucm8sIGZlY2hhLCBwcCkgfD4NCiAgbXV0YXRlKA0KICAgIGlzX2hlYXZ5X2RheSA9IHBwID49IDUwDQogICkNCg0KIyAyLiBDb21wb25lbnRlcyBNZW5zdWFsZXMgcG9yIEVzdGFjacOzbjogU3VtYSBkZSBsYXMgY29tcG9uZW50ZXMNCnJhaW5fbW9udGhfc3RhdGlvbl9jb21wb25lbnRzIDwtIHJhaW5fZGFpbHlfZnVsbCB8Pg0KICBtdXRhdGUoeW0gPSBhcy5EYXRlKGZvcm1hdChmZWNoYSwgIiVZLSVtLTAxIikpKSB8Pg0KICBncm91cF9ieShucm8sIHltLCBpc19oZWF2eV9kYXkpIHw+DQogIHN1bW1hcmlzZSgNCiAgICBwcF9jb21wb25lbnQgPSBzdW0ocHAsIG5hLnJtID0gVFJVRSksDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApDQoNCiMgMy4gUGl2b3RlYXIgeSBDYWxjdWxhciBQcm9tZWRpbyBOYWNpb25hbCAoIkZhaXIiKQ0KcmFpbl9tb250aF9uYXRpb25hbF9zdGFja2VkX2Z1bGwgPC0gcmFpbl9tb250aF9zdGF0aW9uX2NvbXBvbmVudHMgfD4NCiAgdGlkeXI6OnBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBpc19oZWF2eV9kYXksDQogICAgdmFsdWVzX2Zyb20gPSBwcF9jb21wb25lbnQsDQogICAgbmFtZXNfcHJlZml4ID0gInBwXyIsDQogICAgdmFsdWVzX2ZpbGwgPSAwDQogICkgfD4NCiAgcmVuYW1lKHBwX2hlYXZ5ID0gcHBfVFJVRSwgcHBfbGlnaHQgPSBwcF9GQUxTRSkgfD4NCiAgDQogICMgQ2FsY3VsYXIgZWwgUHJvbWVkaW8gTmFjaW9uYWwgKE3DqXRvZG8gRkFJUikNCiAgZ3JvdXBfYnkoeW0pIHw+DQogIHN1bW1hcmlzZSgNCiAgICBwcF9uYXRfbGlnaHQgPSBtZWFuKHBwX2xpZ2h0LCBuYS5ybSA9IFRSVUUpLA0KICAgIHBwX25hdF9oZWF2eSA9IG1lYW4ocHBfaGVhdnksIG5hLnJtID0gVFJVRSksDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApIHw+DQogIGFycmFuZ2UoeW0pDQoNCiMgNC4gR2VuZXJhY2nDs24gZGVsIEdyw6FmaWNvIEFwaWxhZG8gKFBsb3RseSkNCg0KcF9yYWluX2Z1bGwgPC0gcGxvdF9seShyYWluX21vbnRoX25hdGlvbmFsX3N0YWNrZWRfZnVsbCwgeCA9IH55bSkgJT4lDQogICAgYWRkX3RyYWNlKA0KICAgIHkgPSB+cHBfbmF0X2hlYXZ5LA0KICAgIHR5cGUgPSAnYmFyJywNCiAgICBuYW1lID0gJ0VuIGTDrWFzIOKJpSA1MG1tJywNCiAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJyNkNjI3MjgnKQ0KICApICU+JQ0KICBhZGRfdHJhY2UoDQogICAgeSA9IH5wcF9uYXRfbGlnaHQsDQogICAgdHlwZSA9ICdiYXInLA0KICAgIG5hbWUgPSAnRW4gZMOtYXMgPCA1MG1tJywNCiAgICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gJyMxZjc3YjQnKQ0KICApICU+JQ0KICBsYXlvdXQoDQogICAgYmFybW9kZSA9ICdzdGFjaycsDQogICAgdGl0bGUgPSBsaXN0KHRleHQgPSAiQ29udHJpYnVjacOzbiBkZSBkw61hcyBkZSBsbHV2aWEgZnVlcnRlICjiiaUgNTBtbSkgYSBsYSBwcmVjaXBpdGFjacOzbiBtZW5zdWFsPGJyPjxzdXA+UHJvbWVkaW8gZGUgYWN1bXVsYWRvcyBtZW5zdWFsZXMgcG9yIGVzdGFjacOzbiBwYXJhIGVsIHBlcmlvZG8gMTk5MeKAkzIwMjQ8L3N1cD4iKSwNCiAgICB4YXhpcyA9IGxpc3QoDQogICAgICB0aXRsZSA9ICJGZWNoYSIsDQogICAgICBkdGljayA9ICJNMjQiLCAjIE1hcmNhciBjYWRhIDIgYcOxb3MNCiAgICAgIHRpY2tmb3JtYXQgPSAiJVkiDQogICAgKSwNCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiUHJlY2lwaXRhY2nDs24gbWVuc3VhbCBwcm9tZWRpbyAobW0pIiksDQogICAgDQogICAgYW5ub3RhdGlvbnMgPSBsaXN0KA0KICAgICAgbGlzdCh0ZXh0ID0gIkZ1ZW50ZTogU01OIOKAkyBlbGFib3JhY2nDs24gcHJvcGlhIChkYXRvcyBkZSBwcmVjaXBpdGFjacOzbiBkZXNkZSAxOTkxKS4iLA0KICAgICAgICAgICBzaG93YXJyb3cgPSBGQUxTRSwgeHJlZiA9ICJwYXBlciIsIHlyZWYgPSAicGFwZXIiLCB4ID0gMSwgeSA9IC0wLjE1LA0KICAgICAgICAgICB4YW5jaG9yID0gJ3JpZ2h0JywgeWFuY2hvciA9ICdhdXRvJywgZm9udCA9IGxpc3Qoc2l6ZSA9IDEwKSkNCiAgICApLA0KICAgIG1hcmdpbiA9IGxpc3QoYiA9IDgwKQ0KICApDQoNCnBfcmFpbl9mdWxsDQpgYGANCkVuIHByb21lZGlvLCDCv2N1w6FudG9zIG1pbMOtbWV0cm9zIHJlY2liZSBtZW5zdWFsbWVudGUgdW5hIHViaWNhY2nDs24gYWxlYXRvcmlhIGVuIEFyZ2VudGluYSBkZSBsYSBsbHV2aWEgcXVlIHByb3ZpZW5lIGRlIGV2ZW50b3MgdG9ycmVuY2lhbGVzPyBFbCB0YW1hw7FvIGRlIGxhIGJhcnJhIHJvamEgcmVmbGVqYSBsYSByYXJlemEgZGVsIGV2ZW50bywgeWEgcXVlIGVzIGxhIGxhIHN1bWEgZGUgbG9zIG1tIGRlIGxsdXZpYSBmdWVydGUgcG9yIG1lcyBkaXZpZGlvIGVsIG51bWVybyBkZSBlc3RhY2lvbmVzLiBMYSBzdW1hIGRlIGFtYmFzIGJhcnJhcyBlcyBlbCBwcm9tZWRpbyBkZSBwcmVjaXBpdGFjacOzbiBtZW5zdWFsLg0KDQpBIGNvbnRpbnVhY2nDs24sIGNyZWFtb3MgdW4gbWFwYSBwYXJhIHZpc3VhbGl6YXIgZMOzbmRlIG9jdXJyZW4gZXN0b3MgZXZlbnRvcyBleHRyZW1vcy4gTG9zIGPDrXJjdWxvcyByZXByZXNlbnRhbiBlc3RhY2lvbmVzLCBjb24gZWwgdGFtYcOxbyB5IGNvbG9yIGluZGljYW5kbyBsYSBmcmVjdWVuY2lhIGRlIGTDrWFzIGNvbiBsbHV2aWEgPj0gNTAgbW0uIFNlIG9ic2VydmEgdW5hIGNsYXJhIGNvbmNlbnRyYWNpw7NuIGVuIGxhcyByZWdpb25lcyBORUEvTGl0b3JhbC4NCmBgYHtyfQ0KIyBNYXBhIGRlIGV2ZW50b3MgZGUgcHJlY2lwaXRhY2nDs24gZXh0cmVtYSAo4omlIDUwIG1tL2TDrWEpDQojIExsdXZpYSBkaWFyaWEgcG9yIGVzdGFjacOzbi1mZWNoYSArIGxhdC9sb24gY2FsY3VsYWRvcyBlbiBqDQpyYWluX2RhaWx5IDwtIGRmICU+JQ0KICAjIEFncnVwYXIgcG9yIGVzdGFjacOzbiB5IGZlY2hhDQogIGdyb3VwX2J5KG5ybywgbm9tYnJlLCBwcm92aW5jaWEsIGZlY2hhID0gYXMuRGF0ZShkYXRldGltZSkpICU+JQ0KICANCiAgIyBDYWxjdWxhciBwcmVjaXBpdGFjacOzbiBkaWFyaWEgdG90YWwgKHBwKSB5IG9idGVuZXIgY29vcmRzDQogIHN1bW1hcmlzZSgNCiAgICBwcCA9IHN1bShwcmVjaXBfaG9yYXJpYSwgbmEucm0gPSBUUlVFKSwNCiAgICBsYXQgPSBmaXJzdChsYXRpdHVkKSwgIyBFcXVpdmFsZW50ZSBhIGxhdGl0dWRbMUxdDQogICAgbG9uID0gZmlyc3QobG9uZ2l0dWQpLCMgRXF1aXZhbGVudGUgYSBsb25naXR1ZFsxTF0NCiAgICAuZ3JvdXBzID0gJ2Ryb3AnDQogICkNCg0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgMi4gaGVhdnk1MF9kYXk6IEZpbHRyYXIgdW1icmFsICg+PSA1MCBtbS9kw61hKSB5IGNvb3JkcyB2w6FsaWRhcw0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KaGVhdnk1MF9kYXkgPC0gcmFpbl9kYWlseSAlPiUNCiAgZmlsdGVyKHBwID49IDUwICYgaXMuZmluaXRlKGxhdCkgJiBpcy5maW5pdGUobG9uKSkNCg0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgMy4gaGVhdnk1MDogQWdyZWdhciBwb3IgZXN0YWNpw7NuIChjb250YXIgZXZlbnRvcyB5IG3DoXhpbW8gZGlhcmlvKQ0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KaGVhdnk1MCA8LSBoZWF2eTUwX2RheSAlPiUNCiAgIyBBZ3J1cGFyIHBvciBsYXMgY2FyYWN0ZXLDrXN0aWNhcyDDum5pY2FzIGRlIGxhIGVzdGFjacOzbg0KICBncm91cF9ieShucm8sIG5vbWJyZSwgcHJvdmluY2lhKSAlPiUNCiAgDQogICMgQ2FsY3VsYXIgbcOpdHJpY2FzIGRlIGV2ZW50b3MgZXh0cmVtb3MNCiAgc3VtbWFyaXNlKA0KICAgIGV2ZW50b3NfNTBtbSA9IG4oKSwgICAgICAgICAgICMgRXF1aXZhbGVudGUgYSAuTg0KICAgIG1heF9kaWFyaW8gICA9IG1heChwcCwgbmEucm0gPSBUUlVFKSwNCiAgICBsYXQgICAgICAgICAgPSBmaXJzdChsYXQpLCAgICAjIFRvbWFyIGxhIHByaW1lcmEgbGF0aXR1ZCB2w6FsaWRhDQogICAgbG9uICAgICAgICAgID0gZmlyc3QobG9uKSwgICAgIyBUb21hciBsYSBwcmltZXJhIGxvbmdpdHVkIHbDoWxpZGENCiAgICAuZ3JvdXBzID0gJ2Ryb3AnDQogICkNCg0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgNC4gUGFsZXRhIGRlIGNvbG9yIChNw6l0b2RvIFRpZHl2ZXJzZS9MZWFmbGV0KQ0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KcGFsX2V2ZW50cyA8LSBjb2xvck51bWVyaWMoYnJld2VyLnBhbCg5LCAiQmx1ZXMiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBkb21haW4gPSBoZWF2eTUwJGV2ZW50b3NfNTBtbSkNCg0KIyBNYXBhDQptX2V4dCA8LSBsZWFmbGV0KGhlYXZ5NTApICU+JSBhZGRUaWxlcygpICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKA0KICAgIGxuZyA9IH5sb24sIGxhdCA9IH5sYXQsDQogICAgcmFkaXVzID0gfnBtaW4oMTQsIDQgKyBzY2FsZXM6OnJlc2NhbGUoZXZlbnRvc181MG1tLCB0byA9IGMoMiwgMTIpKSksDQogICAgY29sb3IgPSB+cGFsX2V2ZW50cyhldmVudG9zXzUwbW0pLCBmaWxsQ29sb3IgPSB+cGFsX2V2ZW50cyhldmVudG9zXzUwbW0pLA0KICAgIGZpbGxPcGFjaXR5ID0gMC45LCBzdHJva2UgPSBGQUxTRSwNCiAgICBwb3B1cCA9IH5wYXN0ZTAoDQogICAgICAiPGI+Iiwgbm9tYnJlLCAiPC9iPjxicj4iLCBwcm92aW5jaWEsDQogICAgICAiPGJyPkV2ZW50b3Mg4omlNTAgbW0vZMOtYTogIiwgZXZlbnRvc181MG1tLA0KICAgICAgIjxicj5Nw6F4IGRpYXJpbzogIiwgcm91bmQobWF4X2RpYXJpbywgMSksICIgbW0iDQogICAgKQ0KICApICU+JQ0KICANCiAgIyAtLS0gTU9ESUZJQ0FDScOTTiAyOiBUw610dWxvIGRlIGxleWVuZGEgbcOhcyBjb25jaXNvIC0tLQ0KICBhZGRMZWdlbmQoImJvdHRvbXJpZ2h0IiwgcGFsID0gcGFsX2V2ZW50cywgdmFsdWVzID0gfmV2ZW50b3NfNTBtbSwNCiAgICAgICAgICAgIHRpdGxlID0gIkZyZWN1ZW5jaWEgZGUgRXZlbnRvcyIpIA0KICANCiMgLS0tIE1PRElGSUNBQ0nDk04gMzogVMOtdHVsbyBtb3ZpZG8gYSBsYSBlc3F1aW5hIHN1cGVyaW9yIGl6cXVpZXJkYSAodG9wbGVmdCkgLS0tDQp0aXRsZV9leHRfaHRtbCA8LSBodG1sdG9vbHM6OnRhZ3MkZGl2KA0KICBzdHlsZSA9ICJ3aWR0aDogMjgwcHg7IiwNCiAgaHRtbHRvb2xzOjp0YWdzJGg0KCJFdmVudG9zIGRlIHByZWNpcGl0YWNpw7NuIGV4dHJlbWEgKOKJpSA1MCBtbS9kw61hKSIpLA0KICBodG1sdG9vbHM6OnRhZ3MkcCgiTWF5b3IgY29uY2VudHJhY2nDs24gZGUgZXZlbnRvcyBlbiBORUEvTGl0b3JhbC4gRWwgdGFtYcOxbyBkZWwgcHVudG8gZXMgcHJvcG9yY2lvbmFsIGFsIGNvbnRlby4iLCANCiAgICAgICAgICAgICAgICAgICAgc3R5bGUgPSAiZm9udC1zaXplOiBzbWFsbDsiKSwNCiAgaHRtbHRvb2xzOjp0YWdzJGVtKCJGdWVudGU6IFNNTiDigJMgZWxhYm9yYWNpw7NuIHByb3BpYS4iLCBzdHlsZSA9ICJmb250LXNpemU6IHNtYWxsOyIpDQopDQoNCm1fZXh0IDwtIG1fZXh0ICU+JQ0KICBhZGRDb250cm9sKHRpdGxlX2V4dF9odG1sLCBwb3NpdGlvbiA9ICJ0b3BsZWZ0IikNCg0KbV9leHQNCmBgYA0KDQoNCiMjIDguMi4gRGVtYW5kYSBFbmVyZ8OpdGljYSAoR3JhZG9zLUTDrWEpDQoNCkNhbGN1bGFtb3MgbG9zIEdyYWRvcy1Ew61hIGRlIENhbGVmYWNjacOzbiAoSEREKSB5IEVuZnJpYW1pZW50byAoQ0REKS4gRXN0YXMgbcOpdHJpY2FzIGVzdGltYW4gbGEgZGVtYW5kYSBkZSBlbmVyZ8OtYS4NCi0gSEREIChBenVsKTogTWlkZSBjdcOhbnRvIGZyw61vIGhpem8sIGluZGljYW5kbyBsYSBkZW1hbmRhIGRlIGNhbGVmYWNjacOzbi4NCi0gQ0REIChSb2pvKTogTWlkZSBjdcOhbnRvIGNhbG9yIGhpem8sIGluZGljYW5kbyBsYSBkZW1hbmRhIGRlIHJlZnJpZ2VyYWNpw7NuIChhaXJlIGFjb25kaWNpb25hZG8pLg0KRWwgZ3LDoWZpY28gZGUgw6FyZWFzIG11ZXN0cmEgbGEgY2xhcmEgZXN0YWNpb25hbGlkYWQgZGUgbGEgZGVtYW5kYSBlbmVyZ8OpdGljYTogcGljb3MgZGUgSEREIGVuIGludmllcm5vIHkgcGljb3MgZGUgQ0REIGVuIHZlcmFuby4NCg0KYGBge3J9DQojIC0tLSBEZWZpbmljacOzbiBkZSBUZW1wZXJhdHVyYXMgQmFzZSBBanVzdGFkYXMgLS0tDQpUX0JBU0VfQ0FMRU5UQU1JRU5UTyA8LSAxNCAjIFTDrXBpY2EgYmFzZSBhanVzdGFkYSBwYXJhIGNhbGVmYWNjacOzbiAoSEREKQ0KVF9CQVNFX0VORlJJQU1JRU5UTyA8LSAyNCAjIFTDrXBpY2EgYmFzZSBwYXJhIGVuZnJpYW1pZW50byAoQ0REKQ0KDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAxLiBHcmFkb3MtZMOtYSBESUFSSU9TIHBvciBFU1RBQ0nDk04gKENPTiBCQVNFUyBBSlVTVEFEQVMpDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpkZF9zdGF0aW9uX2RheV9tZWpvcmFkbyA8LSBkZiAlPiUNCiAgZmlsdGVyKCFpcy5uYSh0ZW1wKSkgJT4lDQogIGdyb3VwX2J5KG5ybywgZmVjaGEgPSBhcy5EYXRlKGRhdGV0aW1lKSkgJT4lDQogIA0KICAjIEbDs3JtdWxhcyBhanVzdGFkYXMgY29uIFRfQkFTRV9DQUxFTlRBTUlFTlRPICgxNsKwQykgeSBUX0JBU0VfRU5GUklBTUlFTlRPICgyMsKwQykNCiAgc3VtbWFyaXNlKA0KICAgICMgSEREOiBOZWNlc2lkYWQgZGUgQ2FsZWZhY2Npw7NuIChiYXNlIDE2wrBDKQ0KICAgIEhERCA9IHN1bShwbWF4KDAsIFRfQkFTRV9DQUxFTlRBTUlFTlRPIC0gdGVtcCkpIC8gMjQsIA0KICAgIA0KICAgICMgQ0REOiBOZWNlc2lkYWQgZGUgRW5mcmlhbWllbnRvIChiYXNlIDIywrBDKQ0KICAgIENERCA9IHN1bShwbWF4KDAsIHRlbXAgLSBUX0JBU0VfRU5GUklBTUlFTlRPKSkgLyAyNCwgDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApDQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojIDIuIEFjdW11bGFkbyBNZW5zdWFsIHkgMy4gTWVkaWFuYSBOYWNpb25hbA0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCg0KZGRfbW9udGhfbmF0aW9uYWxfbWVqb3JhZG8gPC0gZGRfc3RhdGlvbl9kYXlfbWVqb3JhZG8gJT4lDQogIA0KICAjIDIuIEFjdW11bGFkbyBNZW5zdWFsIHBvciBFc3RhY2nDs24NCiAgZ3JvdXBfYnkoDQogICAgbnJvLCANCiAgICB5bSA9IGZsb29yX2RhdGUoZmVjaGEsICJtb250aCIpIA0KICApICU+JQ0KICBzdW1tYXJpc2UoDQogICAgSEREX21vbnRoID0gc3VtKEhERCwgbmEucm0gPSBUUlVFKSwNCiAgICBDRERfbW9udGggPSBzdW0oQ0RELCBuYS5ybSA9IFRSVUUpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKSAlPiUNCiAgDQogICMgMy4gTWVkaWFuYSBOYWNpb25hbCAiSnVzdGEiDQogIGdyb3VwX2J5KHltKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIEhERF9uYXQgPSBtZWRpYW4oSEREX21vbnRoLCBuYS5ybSA9IFRSVUUpLA0KICAgIENERF9uYXQgPSBtZWRpYW4oQ0REX21vbnRoLCBuYS5ybSA9IFRSVUUpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKSAlPiUNCiAgYXJyYW5nZSh5bSkNCg0KIyAtLS0NCiMgNC4gR3LDoWZpY28gZGUgw4FSRUEgKExhIGFsdGVybmF0aXZhKQ0KIyAtLS0NCnBfZGQgPC0gcGxvdF9seShkZF9tb250aF9uYXRpb25hbCwgeCA9IH55bSkgJT4lDQogIA0KICAjIENhcGEgZGUgQ2FsZWZhY2Npw7NuIChIREQpDQogIGFkZF90cmFjZSgNCiAgICB5ID0gfkhERF9uYXQsIA0KICAgIG5hbWUgPSAiQ2FsZWZhY2Npw7NuIChIREQpIiwgDQogICAgdHlwZSA9ICdzY2F0dGVyJywgDQogICAgbW9kZSA9ICdsaW5lcycsDQogICAgbGluZSA9IGxpc3QoY29sb3IgPSAnIzFmNzdiNCcpLCAjIEF6dWwNCiAgICBmaWxsID0gJ3RvemVyb3knLA0KICAgIGZpbGxjb2xvciA9ICdyZ2JhKDMxLCAxMTksIDE4MCwgMC40KScgIyBNaXNtbyBhenVsLCBjb24gdHJhbnNwYXJlbmNpYQ0KICApICU+JQ0KICANCiAgIyBDYXBhIGRlIFJlZnJpZ2VyYWNpw7NuIChDREQpDQogIGFkZF90cmFjZSgNCiAgICB5ID0gfkNERF9uYXQsIA0KICAgIG5hbWUgPSAiUmVmcmlnZXJhY2nDs24gKENERCkiLCANCiAgICB0eXBlID0gJ3NjYXR0ZXInLCANCiAgICBtb2RlID0gJ2xpbmVzJywNCiAgICBsaW5lID0gbGlzdChjb2xvciA9ICcjZDYyNzI4JyksICMgUm9qby9OYXJhbmphDQogICAgZmlsbCA9ICd0b3plcm95JywgIyA8LS0gwqFMQSBNQUdJQSBFU1TDgSBBUVXDjSENCiAgICBmaWxsY29sb3IgPSAncmdiYSgyMTQsIDM5LCA0MCwgMC40KScgIyBNaXNtbyByb2pvLCBjb24gdHJhbnNwYXJlbmNpYQ0KICApICU+JQ0KICANCiAgIyAtLS0gTGF5b3V0IC0tLQ0KICBsYXlvdXQoDQogICAgdGl0bGUgPSBsaXN0KHRleHQgPSAiTGEgZGVtYW5kYSBlbmVyZ8OpdGljYSBwb3RlbmNpYWwgcG9yIGNsaW1hIHNpZ3VlIHVuIGZ1ZXJ0ZSBjaWNsbyBlc3RhY2lvbmFsPGJyPjxzdXA+TWVkaWFuYSBkZSBHcmFkb3MtRMOtYSAoSEREL0NERCkgcG9yIGVzdGFjacOzbiwgdW1icmFsIDE4wrBDPC9zdXA+IiksDQogICAgeGF4aXMgPSBsaXN0KA0KICAgICAgdGl0bGUgPSAiRmVjaGEiLA0KICAgICAgZHRpY2sgPSAiTTEyIiwNCiAgICAgIHRpY2tmb3JtYXQgPSAiJWIgJVkiDQogICAgKSwNCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiR3JhZG9zLWTDrWEgYWN1bXVsYWRvcyAoTWVkaWFuYSkiKSwgDQogICAgbGVnZW5kID0gbGlzdCh0aXRsZSA9IGxpc3QodGV4dD0nPGI+IERlbWFuZGEgPC9iPicpKSwNCiAgICBhbm5vdGF0aW9ucyA9IGxpc3QoDQogICAgICBsaXN0KHRleHQgPSAiRnVlbnRlOiBTTU4g4oCTIGVsYWJvcmFjacOzbiBwcm9waWEuIiwNCiAgICAgICAgICAgc2hvd2Fycm93ID0gRkFMU0UsIHhyZWYgPSAicGFwZXIiLCB5cmVmID0gInBhcGVyIiwgeCA9IDEsIHkgPSAtMC4xNSwNCiAgICAgICAgICAgeGFuY2hvciA9ICdyaWdodCcsIHlhbmNob3IgPSAnYXV0bycsIGZvbnQgPSBsaXN0KHNpemUgPSAxMCkpDQogICAgKSwNCiAgICBtYXJnaW4gPSBsaXN0KGIgPSA4MCkNCiAgKQ0KcF9kZA0KYGBgDQoNCiMjIDguMy4gVmllbnRvIChSb3NhIGRlIFZpZW50b3MpDQoNCkdlbmVyYW1vcyB1bmEgIlJvc2EgZGUgVmllbnRvcyIgcGFyYSBsYSBlc3RhY2nDs24gY29uIG3DoXMgcmVnaXN0cm9zLiBFc3RlIGdyw6FmaWNvIGVzcGVjaWFsaXphZG8gbXVlc3RyYSBsYSBmcmVjdWVuY2lhIGUgaW50ZW5zaWRhZCBkZWwgdmllbnRvIHBvciBkaXJlY2Npw7NuLiBFbiBlc3RlIGVqZW1wbG8sIHNlIG9ic2VydmEgdW4gY2xhcm8gcHJlZG9taW5pbyBkZSB2aWVudG9zIGRlbCBOb3J0ZSB5IFN1ZGVzdGUuDQpgYGB7cn0NCiMgPT09PT09PT09PT09PT09PT09PT0gUk9TQSBERSBWSUVOVE9TID09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAxLiBJZGVudGlmaWNhciBsYSBlc3RhY2nDs24gY29uIG3DoXMgZGF0b3MNCnN0IDwtIGRmICU+JQ0KICBjb3VudChub21icmUsIHNvcnQgPSBUUlVFKSAlPiUNCiAgc2xpY2UoMSkgJT4lDQogIHB1bGwobm9tYnJlKSAjIEV4dHJhZXIgZWwgdmFsb3IgY29tbyB1biB2ZWN0b3Igc2ltcGxlDQoNCiMgMi4gRmlsdHJhciBlbCBkYXRhIGZyYW1lIG9yaWdpbmFsIHBhcmEgb2J0ZW5lciBsb3MgZGF0b3MgZGUgdmllbnRvDQp3aW5kX2RmIDwtIGRmICU+JQ0KICBmaWx0ZXIobm9tYnJlID09IHN0KSAlPiUNCiAgc2VsZWN0KA0KICAgIHdzID0gZmYsICAgICAjIFZlbG9jaWRhZCBkZWwgdmllbnRvICh3aW5kIHNwZWVkKQ0KICAgIHdkID0gZGQsICAgICAjIERpcmVjY2nDs24gZGVsIHZpZW50byAod2luZCBkaXJlY3Rpb24pDQogICAgZGF0ZSA9IGRhdGV0aW1lICMgRmVjaGEgeSBob3JhDQogICkNCg0KY2F0KHNwcmludGYoIlJvc2EgZGUgdmllbnRvcyBwYXJhIGVzdGFjacOzbiAlczpcbiIsIHN0KSkNCg0KIyBEZWZpbmljacOzbiBkZSBsb3MgYnJlYWtzIG9yaWdpbmFsZXMNCm15X2JyZWFrcyA8LSBjKDAsIDIsIDQsIDYsIDgsIDEyLCAyMCwgNDgpDQoNCiMgLS0tIFRVIE5VRVZBIEVTVFJBVEVHSUEgREUgTEFCRUxTIC0tLQ0KIyBDcmVhbW9zIDggbGFiZWxzIChldGlxdWV0YXMpLCB1bmEgcGFyYSBDQURBIHB1bnRvIGRlIGJyZWFrDQojICgwLCAyLCA0LCA2LCA4LCAxMiwgMjAsIHkgZWwgZmluYWwgNDgpDQpteV9rZXlfbGFiZWxzIDwtIGMoIjAiLCAiMiIsICI0IiwgIjYiLCAiOCIsICIxMiIsICIyMCIsICI0OCIpDQoNCndpbmRfcGxvdCA8LSBvcGVuYWlyOjp3aW5kUm9zZSgNCiAgd2luZF9kZiwNCiAgd3MgPSAid3MiLA0KICB3ZCA9ICJ3ZCIsDQogIGJyZWFrcyA9IG15X2JyZWFrcywgIyBMb3MgYnJlYWtzIHBhcmEgZWwgY8OhbGN1bG8NCiAgcGFkZGxlID0gRkFMU0UsDQogIGtleS5wb3NpdGlvbiA9ICJib3R0b20iLA0KICBhdXRvLnRleHQgPSBGQUxTRSwNCiAgDQogIG1haW4gPSBwYXN0ZSgiUHJlZG9taW5pbyBkZSB2aWVudG9zIGRlbCBOb3J0ZSB5IFN1ZGVzdGUgZW4gbGEgZXN0YWNpw7NuIiwgYXMuY2hhcmFjdGVyKHN0KSksDQogIHN1YiA9ICJMYSBmcmVjdWVuY2lhIHkgbGEgaW50ZW5zaWRhZCAobS9zKSBkZWZpbmVuIGVsIHBhdHLDs24gbG9jYWwuXG5GdWVudGU6IFNNTiDigJMgZWxhYm9yYWNpw7NuIHByb3BpYS4iLA0KDQogICMgLS0tIEFKVVNURSBDTEFWRTogVXNhciBlbCBhcmd1bWVudG8gJ2tleScgY29tbyB1bmEgbGlzdGEgLS0tDQogICMgRXN0byBsZSBwYXNhIG9wY2lvbmVzIGRpcmVjdGFtZW50ZSBhIGxhIGZ1bmNpw7NuICdkcmF3LmNvbG9ya2V5JyBkZSBsYXR0aWNlDQogIGtleSA9IGxpc3QoDQogICAgIyAnYXQnIGxlIGRpY2UgZMOzbmRlIHBvbmVyIGxvcyAndGlja3MnIGRlIGNvbG9yIChsb3MgOCB2YWxvcmVzIGRlIGJyZWFrKQ0KICAgIGF0ID0gbXlfYnJlYWtzLCANCiAgICANCiAgICAjICdsYWJlbHMnIGxlIGRpY2UgcXXDqSB0ZXh0byBwb25lciBlbiBlc29zICd0aWNrcycgKGxhcyA4IGV0aXF1ZXRhcyBxdWUgY3JlYW1vcykNCiAgICBsYWJlbHMgPSBteV9rZXlfbGFiZWxzLA0KICAgIA0KICAgICMgJ2NleCcgY29udHJvbGEgZWwgdGFtYcOxbyBkZSBsYSBmdWVudGUgZGUgbG9zIGxhYmVscyBkZSBsYSBsZXllbmRhDQogICAgY2V4ID0gMC45LCAjIFJlZHVjaW1vcyB1biBwb2NvIGxhIGZ1ZW50ZSBwYXJhIHF1ZSBxdWVwYSBiaWVuDQogICAgDQogICAgIyAnc3BhY2UnIGNvbnRyb2xhIGTDs25kZSB2YSBsYSBsZXllbmRhDQogICAgc3BhY2UgPSAiYm90dG9tIg0KICApLA0KICANCiAgIyBBIG9wZW5haXIgbGUgZ3VzdGEgdGVuZXIgdW4ga2V5LmZvb3RlciBwYXJhIGxhcyB1bmlkYWRlcw0KICBrZXkuZm9vdGVyID0gIihtL3MpIiwNCiAgDQogICMgTWFudGVuZXIgbG9zIGFqdXN0ZXMgZGUgZXNwYWNpbyBxdWUgYXJyZWdsYXJvbiBlbCB0w610dWxvL3N1YnTDrXR1bG8NCiAgcGFyLnNldHRpbmdzID0gbGlzdCgNCiAgICBsYXlvdXQuaGVpZ2h0cyA9IGxpc3QoDQogICAgICBib3R0b20ucGFkZGluZyA9IDQsIA0KICAgICAgc3ViID0gNCAgICAgICAgICAgICANCiAgICApLA0KICAgIGZvbnRzaXplID0gbGlzdCh0ZXh0ID0gOSwgcG9pbnRzID0gOSkgIyBNYW50ZW5lbW9zIGxhIGZ1ZW50ZSBsZWdpYmxlDQogICkNCikNCg0Kd2luZF9wbG90DQpgYGANCg0KTGxldmFtb3MgZXN0byB1biBwYXNvIG3DoXMgYWxsw6EgeSBjcmVhbW9zIHVuIG1hcGEgaW50ZXJhY3Rpdm8gZGUgUm9zYXMgZGUgVmllbnRvLg0KLSBFbCBzY3JpcHQgZ2VuZXJhIHVuIFBORyBtaW5pYXR1cmEgZGUgbGEgcm9zYSBkZSB2aWVudG9zIHBhcmEgY2FkYSBlc3RhY2nDs24uDQotIEVsIG1hcGEgYWdydXBhIGVzdGFzIHJvc2FzIGVuIGNsw7pzdGVyZXMuIEFsIGhhY2VyIHpvb20sIGxvcyBjbMO6c3RlcmVzIHNlIGV4cGFuZGVuICgic3BpZGVyZnkiKS4NCi0gVW5hIGNhcGEgZGUgIlJlc3VtZW4iIG11ZXN0cmEgY8OtcmN1bG9zIHNpbXBsZXMgY29sb3JlYWRvcyBwb3IgbGEgdmVsb2NpZGFkIG1lZGlhIGRlbCB2aWVudG8uDQotIEFsIGhhY2VyIGNsaWMgZW4gdW5hIHJvc2EgbWluaWF0dXJhLCBhcGFyZWNlIHVuYSB2ZXJzacOzbiBtw6FzIGdyYW5kZSB5IGRldGFsbGFkYSBlbiBlbCBwb3AtdXAuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoNDIpICMgUGFyYSByZXByb2R1Y2liaWxpZGFkIGRlbCBjbHVzdGVyaW5nDQoNCiMgLS0tIDEpIERlZmluaWNpw7NuIGRlIEVzdGlsbyAoRWwgcXVlIHRlIGd1c3TDsykgLS0tDQpvdXRwdXRfZGlyIDwtICJ3aW5kX3Jvc2VzIg0KaWYgKCFkaXIuZXhpc3RzKG91dHB1dF9kaXIpKSB7DQogIGRpci5jcmVhdGUob3V0cHV0X2RpcikNCn0NCg0KIyBCcmVha3MgeSBMYWJlbHMgcXVlIGRlZmluaW1vcyAocGFyYSBsYSBsZXllbmRhICdrZXkgPSBsaXN0JykNCm15X2JyZWFrcyA8LSBjKDAsIDIsIDQsIDYsIDgsIDEyLCAyMCwgNDgpDQpteV9rZXlfbGFiZWxzIDwtIGMoIjAiLCAiMiIsICI0IiwgIjYiLCAiOCIsICIxMiIsICIyMCIsICI0OCIpDQoNCiMgLS0tIDIpIEluZm9ybWFjacOzbiBkZSBFc3RhY2lvbmVzIChkcGx5cikgLS0tDQpzdGF0aW9uc19pbmZvIDwtIGRmICU+JQ0KICBmaWx0ZXIoaXMuZmluaXRlKGxhdGl0dWQpICYgaXMuZmluaXRlKGxvbmdpdHVkKSkgJT4lDQogIGdyb3VwX2J5KG5ybywgbm9tYnJlLCBwcm92aW5jaWEpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbGF0ID0gZmlyc3QobGF0aXR1ZCksDQogICAgbG9uID0gZmlyc3QobG9uZ2l0dWQpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKSAlPiUNCiAgZmlsdGVyKGlzLmZpbml0ZShsYXQpICYgaXMuZmluaXRlKGxvbikpDQoNCiMgLS0tIDMpIEZ1bmNpw7NuICdtYWtlX3dpbmRfcm9zZV9wbmcnIChDT1JSRUdJREEpIC0tLQ0KDQptYWtlX3dpbmRfcm9zZV9wbmcgPC0gZnVuY3Rpb24oc3RuX2R0LCBzdF9uYW1lLCBmaWxlX3BuZywgd2lkdGgsIGhlaWdodCwgcmVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzX3RodW1iID0gVFJVRSkgew0KICANCiAgZ3JEZXZpY2VzOjpwbmcoZmlsZW5hbWUgPSBmaWxlX3BuZywgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0LCByZXMgPSByZXMsIGJnID0gInRyYW5zcGFyZW50IikNCiAgb24uZXhpdChnckRldmljZXM6OmRldi5vZmYoKSwgYWRkID0gVFJVRSkNCiAgDQogIGlmIChpc190aHVtYikgew0KICAgICMgLS0tIFRIVU1CTkFJTCAoTcOpdG9kbyByb2J1c3RvKSAtLS0NCiAgICAjIE9jdWx0YW1vcyB0b2RvIHBhcmEgZWwgw61jb25vDQogICAgb3BlbmFpcjo6d2luZFJvc2UoDQogICAgICBzdG5fZHQsIHdzID0gImZmIiwgd2QgPSAiZGQiLA0KICAgICAgYnJlYWtzID0gbXlfYnJlYWtzLA0KICAgICAgcGFkZGxlID0gRkFMU0UsIA0KICAgICAgYXV0by50ZXh0ID0gRkFMU0UsDQogICAgICBrZXkgPSBGQUxTRSwgICAgICAjIE9jdWx0YSBsYSBsZXllbmRhDQogICAgICBtYWluID0gIiAiLCAgICAgICAjIE9jdWx0YSB0w610dWxvcw0KICAgICAgc3ViID0gIiAiLA0KICAgICAgeGxhYiA9ICIgIiwNCiAgICAgIHlsYWIgPSAiICINCiAgICApDQogICAgDQogIH0gZWxzZSB7DQogICAgIyAtLS0gRlVMTCBQTkcgKEFwbGljYW5kbyBsYSBzb2x1Y2nDs24gcXVlIHBlZGlzdGUpIC0tLQ0KICAgICMgKEVzdGUgZXMgZWwgYmxvcXVlIGRlIGPDs2RpZ28gZXhhY3RvIGRlIHR1IGVqZW1wbG8pDQogICAgb3BlbmFpcjo6d2luZFJvc2UoDQogICAgICBzdG5fZHQsDQogICAgICB3cyA9ICJmZiIsIA0KICAgICAgd2QgPSAiZGQiLCANCiAgICAgIGJyZWFrcyA9IG15X2JyZWFrcywNCiAgICAgIHBhZGRsZSA9IEZBTFNFLA0KICAgICAga2V5LnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICBhdXRvLnRleHQgPSBGQUxTRSwNCiAgICAgIA0KICAgICAgbWFpbiA9IHBhc3RlKCJSb3NhIGRlIFZpZW50bzoiLCBzdF9uYW1lKSwNCiAgICAgIHN1YiA9ICJGcmVjdWVuY2lhIGUgaW50ZW5zaWRhZCAobS9zKS4gRnVlbnRlOiBTTU4iLA0KICAgICAgDQogICAgICAjIC0tLSBBSlVTVEUgQ0xBVkU6IFVzYXIgZWwgYXJndW1lbnRvICdrZXknIC0tLQ0KICAgICAga2V5ID0gbGlzdCgNCiAgICAgICAgYXQgPSBteV9icmVha3MsIA0KICAgICAgICBsYWJlbHMgPSBteV9rZXlfbGFiZWxzLCAjIExhYmVscyBsaW1waW9zICgiMCIsICIyIi4uLikNCiAgICAgICAgY2V4ID0gMC45LA0KICAgICAgICBzcGFjZSA9ICJib3R0b20iDQogICAgICApLA0KICAgICAgDQogICAgICBrZXkuZm9vdGVyID0gIihtL3MpIiwNCiAgICAgIA0KICAgICAgIyBBanVzdGVzIGRlIG3DoXJnZW5lcyB5IGZ1ZW50ZXMNCiAgICAgIHBhci5zZXR0aW5ncyA9IGxpc3QoDQogICAgICAgIGxheW91dC5oZWlnaHRzID0gbGlzdCgNCiAgICAgICAgICBib3R0b20ucGFkZGluZyA9IDQsIA0KICAgICAgICAgIHN1YiA9IDQNCiAgICAgICAgKSwNCiAgICAgICAgZm9udHNpemUgPSBsaXN0KHRleHQgPSA5LCBwb2ludHMgPSA5KQ0KICAgICAgKQ0KICAgICkNCiAgfQ0KfQ0KDQojIC0tLSA0KSBCdWNsZSBkZSBHZW5lcmFjacOzbiAoVGh1bWIgKyBGdWxsKSAtLS0NCnBuZ190aHVtYl9saXN0IDwtIGxpc3QoKQ0KcG5nX2Z1bGxfbGlzdCA8LSBsaXN0KCkNCmNhdCgiR2VuZXJhbmRvIiwgbnJvdyhzdGF0aW9uc19pbmZvKSwgInBhcmVzIGRlIHJvc2FzIGRlIHZpZW50byBlbiIsIG91dHB1dF9kaXIsICIuLi5cbiIpDQoNCmZvciAoaSBpbiBzZXFfbGVuKG5yb3coc3RhdGlvbnNfaW5mbykpKSB7DQogIGlkICAgICAgPC0gc3RhdGlvbnNfaW5mbyRucm9baV0NCiAgc3RfbmFtZSA8LSBzdGF0aW9uc19pbmZvJG5vbWJyZVtpXQ0KICANCiAgc3RuX2R0IDwtIGRmICU+JQ0KICAgIGZpbHRlcihucm8gPT0gaWQsIGlzLmZpbml0ZShmZiksIGlzLmZpbml0ZShkZCkpICU+JQ0KICAgIHNlbGVjdChmZiwgZGQpDQogIA0KICBpZiAoIW5yb3coc3RuX2R0KSkgbmV4dA0KICANCiAgY2xlYW5fbmFtZSA8LSBqYW5pdG9yOjptYWtlX2NsZWFuX25hbWVzKHN0X25hbWUpDQogIGZfdGh1bWIgPC0gZmlsZS5wYXRoKG91dHB1dF9kaXIsIHNwcmludGYoIiVzX3RodW1iLnBuZyIsIGNsZWFuX25hbWUpKQ0KICBmX2Z1bGwgIDwtIGZpbGUucGF0aChvdXRwdXRfZGlyLCBzcHJpbnRmKCIlc19mdWxsLnBuZyIsICBjbGVhbl9uYW1lKSkNCiAgDQogICMgR2VuZXJhciBQTkcgbWluaWF0dXJhIChwZXF1ZcOxbyB5IGxpbXBpbykNCiAgbWFrZV93aW5kX3Jvc2VfcG5nKHN0bl9kdCwgc3RfbmFtZSwgZl90aHVtYiwgd2lkdGggPSAxMDAsIGhlaWdodCA9IDEwMCwgcmVzID0gNzIsIGlzX3RodW1iID0gVFJVRSkNCiAgIyBHZW5lcmFyIFBORyBjb21wbGV0byAoTcOBUyBHUkFOREU6IDQwMHg0MDAgcGFyYSBxdWUgZW50cmVuIHTDrXR1bG9zKQ0KICBtYWtlX3dpbmRfcm9zZV9wbmcoc3RuX2R0LCBzdF9uYW1lLCBmX2Z1bGwsICB3aWR0aCA9IDQwMCwgaGVpZ2h0ID0gNDAwLCByZXMgPSA5NiwgaXNfdGh1bWIgPSBGQUxTRSkNCiAgDQogIHBuZ190aHVtYl9saXN0W1thcy5jaGFyYWN0ZXIoaWQpXV0gPC0gZl90aHVtYg0KICBwbmdfZnVsbF9saXN0W1thcy5jaGFyYWN0ZXIoaWQpXV0gPC0gZl9mdWxsDQp9DQoNCiMgLS0tIDUpIENvbWJpbmFyIHkgQ29kaWZpY2FyIGVuIEJhc2U2NCAtLS0NCnRodW1iX2RmIDwtIHRpYmJsZShucm9fc3RyID0gbmFtZXMocG5nX3RodW1iX2xpc3QpLCBwbmdfdGh1bWIgPSB1bmxpc3QocG5nX3RodW1iX2xpc3QpKQ0KZnVsbF9kZiAgPC0gdGliYmxlKG5yb19zdHIgPSBuYW1lcyhwbmdfZnVsbF9saXN0KSwgIHBuZ19mdWxsICA9IHVubGlzdChwbmdfZnVsbF9saXN0KSkNCg0KbWFwX2RhdGEgPC0gc3RhdGlvbnNfaW5mbyAlPiUNCiAgbXV0YXRlKG5yb19zdHIgPSBhcy5jaGFyYWN0ZXIobnJvKSkgJT4lDQogIGxlZnRfam9pbih0aHVtYl9kZiwgYnkgPSAibnJvX3N0ciIpICU+JQ0KICBsZWZ0X2pvaW4oZnVsbF9kZiwgIGJ5ID0gIm5yb19zdHIiKSAlPiUNCiAgc2VsZWN0KC1ucm9fc3RyKSAlPiUNCiAgZmlsdGVyKGZpbGUuZXhpc3RzKHBuZ190aHVtYikgJiBmaWxlLmV4aXN0cyhwbmdfZnVsbCkpDQoNCnN0b3BpZm5vdChucm93KG1hcF9kYXRhKSA+IDApDQoNCm1hcF9kYXRhIDwtIG1hcF9kYXRhICU+JQ0KICByb3d3aXNlKCkgJT4lDQogIG11dGF0ZSgNCiAgICBwbmdfdGh1bWJfZGF0YSA9IGJhc2U2NGVuYzo6ZGF0YVVSSShmaWxlID0gcG5nX3RodW1iLCBtaW1lID0gImltYWdlL3BuZyIpLA0KICAgIHBuZ19mdWxsX2RhdGEgID0gYmFzZTY0ZW5jOjpkYXRhVVJJKGZpbGUgPSBwbmdfZnVsbCwgIG1pbWUgPSAiaW1hZ2UvcG5nIikNCiAgKSAlPiUNCiAgdW5ncm91cCgpDQoNCiMgLS0tIDYpIENhcGEgInJlc3VtZW4iIChDw61yY3Vsb3MpIC0tLQ0KIyAoQ09SUkVHSURPOiAnbG9uJyBlbiBsdWdhciBkZSAnTG8nKQ0Kc3RhdGlvbnNfc3BlZWQgPC0gZGYgJT4lDQogIGZpbHRlcihpcy5maW5pdGUoZmYpKSAlPiUNCiAgZ3JvdXBfYnkobnJvKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIHdzX21lYW4gPSBtZWFuKGZmLCBuYS5ybSA9IFRSVUUpLA0KICAgIGxhdCA9IGZpcnN0KGxhdGl0dWQpLCANCiAgICBsb24gPSBmaXJzdChsb25naXR1ZCksICMgPC0tIMKhRXJyb3IgY29ycmVnaWRvIQ0KICAgIG5vbWJyZSA9IGZpcnN0KG5vbWJyZSksIA0KICAgIHByb3ZpbmNpYSA9IGZpcnN0KHByb3ZpbmNpYSksDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApICU+JQ0KICBmaWx0ZXIoaXMuZmluaXRlKGxhdCkgJiBpcy5maW5pdGUobG9uKSkNCg0KcGFsX3dzIDwtIGxlYWZsZXQ6OmNvbG9yTnVtZXJpYygiWWxHbkJ1IiwgZG9tYWluID0gc3RhdGlvbnNfc3BlZWQkd3NfbWVhbikNCg0KIyAtLS0gNykgTWFwYSBMZWFmbGV0IChDbMO6c3RlciArIFBvcHVwKSAtLS0NCmljb25fcm9zZSA8LSBsZWFmbGV0OjppY29ucygNCiAgaWNvblVybCAgID0gbWFwX2RhdGEkcG5nX3RodW1iX2RhdGEsICMgRWwgw61jb25vIGVzIGxhIG1pbmlhdHVyYSBsaW1waWENCiAgaWNvbldpZHRoID0gNzIsIGljb25IZWlnaHQgPSA3MiwgICAgICAjIFRhbWHDsW8gcXVlIHBlcm1pdGUgbGEgZXhwYW5zacOzbiAoc3BpZGVyZnkpDQogIGNsYXNzTmFtZSA9ICJyb3NlIg0KKQ0KDQptIDwtIGxlYWZsZXQ6OmxlYWZsZXQoKSAlPiUNCiAgbGVhZmxldDo6YWRkUHJvdmlkZXJUaWxlcyhsZWFmbGV0Ojpwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIA0KICAjIENhcGEgUmVzdW1lbiAoY8OtcmN1bG9zKQ0KICBsZWFmbGV0OjphZGRDaXJjbGVNYXJrZXJzKA0KICAgIGRhdGEgPSBzdGF0aW9uc19zcGVlZCwgZ3JvdXAgPSAiUmVzdW1lbiAoY8OtcmN1bG9zKSIsDQogICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgcmFkaXVzID0gNiwNCiAgICBmaWxsQ29sb3IgPSB+cGFsX3dzKHdzX21lYW4pLCBmaWxsT3BhY2l0eSA9IDAuOCwgc3Ryb2tlID0gRkFMU0UsDQogICAgcG9wdXAgPSB+cGFzdGUwKCI8Yj4iLCBub21icmUsICI8L2I+PGJyPiIsIHByb3ZpbmNpYSwNCiAgICAgICAgICAgICAgICAgICAgIjxicj5WaWVudG8gbWVkaW86ICIsIHJvdW5kKHdzX21lYW4sIDEpLCAiIG0vcyIpDQogICkgJT4lDQogIGxlYWZsZXQ6OmFkZExlZ2VuZCgiYm90dG9tcmlnaHQiLCBwYWwgPSBwYWxfd3MsIHZhbHVlcyA9IHN0YXRpb25zX3NwZWVkJHdzX21lYW4sDQogICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJWZWxvY2lkYWQgZGVsIHZpZW50byAobS9zKSIsIGdyb3VwID0gIlJlc3VtZW4gKGPDrXJjdWxvcykiKSAlPiUNCiAgDQogICMgQ2FwYSBEZXRhbGxlIChyb3NhcykNCiAgbGVhZmxldDo6YWRkTWFya2VycygNCiAgICBkYXRhID0gbWFwX2RhdGEsIGdyb3VwID0gIlJvc2FzIChkZXRhbGxlKSIsDQogICAgbG5nID0gfmxvbiwgbGF0ID0gfmxhdCwgaWNvbiA9IGljb25fcm9zZSwgIyDDjWNvbm8gPSB0aHVtYg0KICAgIA0KICAgICMgUG9wdXAgPSBmdWxsDQogICAgcG9wdXAgPSB+c3ByaW50ZigiPGI+RXN0YWNpb25hbGlkYWQgKCVzKTwvYj48YnI+PGltZyBzcmM9JyVzJyB3aWR0aD0nMjgwJz4iLCANCiAgICAgICAgICAgICAgICAgICAgIG5vbWJyZSwgcG5nX2Z1bGxfZGF0YSksDQogICAgDQogICAgb3B0aW9ucyA9IGxlYWZsZXQ6Om1hcmtlck9wdGlvbnMoekluZGV4T2Zmc2V0ID0gMTAwMCksDQogICAgY2x1c3Rlck9wdGlvbnMgPSBsZWFmbGV0OjptYXJrZXJDbHVzdGVyT3B0aW9ucygNCiAgICAgIGRpc2FibGVDbHVzdGVyaW5nQXRab29tID0gOCwgDQogICAgICBzcGlkZXJmeU9uTWF4Wm9vbSA9IFRSVUUsICAgICAjIExhIGV4cGFuc2nDs24gRlVOQ0lPTkFSw4ENCiAgICAgIHNob3dDb3ZlcmFnZU9uSG92ZXIgPSBGQUxTRSwNCiAgICAgIG1heENsdXN0ZXJSYWRpdXMgPSA2MA0KICAgICkNCiAgKSAlPiUNCiAgbGVhZmxldDo6YWRkTGF5ZXJzQ29udHJvbCgNCiAgICBvdmVybGF5R3JvdXBzID0gYygiUmVzdW1lbiAoY8OtcmN1bG9zKSIsICJSb3NhcyAoZGV0YWxsZSkiKSwNCiAgICBvcHRpb25zID0gbGVhZmxldDo6bGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpDQogICkgJT4lDQogIGxlYWZsZXQ6OmhpZGVHcm91cCgiUm9zYXMgKGRldGFsbGUpIikNCg0KIyBNb3N0cmFyIGVsIG1hcGENCm0NCmBgYA0KDQojIDkuIERpc3BlcnNpw7NuIHkgTW9kZWxhZG8NCiMjIDkuMS4gRGlzcGVyc2nDs24gZW50cmUgRXN0YWNpb25lcw0KDQpFc3RlICJncsOhZmljbyBkZSBjaW50YSIgKHJpYmJvbiBwbG90KSBtdWVzdHJhIGxhIHNlcmllIHRlbXBvcmFsIG5hY2lvbmFsLCBwZXJvIGVuIGx1Z2FyIGRlIHNvbG8gbGEgbWVkaWEsIGdyYWZpY2EgbGEgbWVkaWFuYSAoUDUwKSB5IGVsIHJhbmdvIGRlbCA4MCUgKFAxMC1QOTApIGRlIHRvZGFzIGxhcyBlc3RhY2lvbmVzLg0KDQpMYSBjaW50YSAow6FyZWEgc29tYnJlYWRhKSByZXByZXNlbnRhIGxhIGRpc3BlcnNpw7NuIG8gdmFyaWFiaWxpZGFkIGVudHJlIGxhcyBkaWZlcmVudGVzIHJlZ2lvbmVzIGRlbCBwYcOtcy4gU2UgcHVlZGUgdmVyIHF1ZSBsYSB2YXJpYWJpbGlkYWQgZXMgbWF5b3IgZW4gaW52aWVybm8gKGNpbnRhIG3DoXMgYW5jaGEpLg0KYGBge3J9DQojIFJpYmJvbnMgZGUgcGVyY2VudGlsZXMgbmFjaW9uYWxlcyAoZGlzcGVyc2nDs24gZW50cmUgZXN0YWNpb25lcykNCm5hdF9wZXJjZW50aWxlcyA8LSBtb250aGx5X2J5X3N0YXRpb25fZmFpciAlPiUNCiAgZ3JvdXBfYnkoeW0pICU+JQ0KICBzdW1tYXJpc2UoDQogICAgIyBMYSBjb2x1bW5hICd5JyBjb250aWVuZSBsYSB0ZW1wZXJhdHVyYSBtZWRpYSBtZW5zdWFsIHBvciBlc3RhY2nDs24NCiAgICBwMTAgPSBxdWFudGlsZSh5LCAwLjEwLCBuYS5ybSA9IFRSVUUpLA0KICAgIHA1MCA9IHF1YW50aWxlKHksIDAuNTAsIG5hLnJtID0gVFJVRSksDQogICAgcDkwID0gcXVhbnRpbGUoeSwgMC45MCwgbmEucm0gPSBUUlVFKSwNCiAgICBuX2VzdCA9IG4oKSwgDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApICU+JQ0KICAjIE9yZGVuYXIgcG9yIGZlY2hhDQogIGFycmFuZ2UoeW0pDQoNCnBfcmliYm9uIDwtIHBsb3RfbHkobmF0X3BlcmNlbnRpbGVzLCB4PX55bSkgJT4lDQogICAgDQogICAgIyAxLiBCYW5kYSBkZSBWYXJpYWJpbGlkYWQgKHAxMC1wOTApDQogICAgYWRkX3JpYmJvbnMoeW1pbiA9IH5wMTAsIHltYXggPSB+cDkwLCANCiAgICAgICAgICAgICAgICBuYW1lID0gIlJhbmdvIDgwJSAocDEw4oCTcDkwKSIsIA0KICAgICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjI1LA0KICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJ3RyYW5zcGFyZW50JyksIA0KICAgICAgICAgICAgICAgIGZpbGxjb2xvciA9ICdsaWdodGJsdWUnKSAlPiUgDQogICAgDQogICAgIyAyLiBMw61uZWEgQ2VudHJhbCAoTWVkaWFuYSkNCiAgICBhZGRfdHJhY2UoeT1+cDUwLCBuYW1lPSJNZWRpYW5hIChQNTApIiwgbW9kZT0ibGluZXMiLA0KICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICcjMWY3N2I0Jywgd2lkdGggPSAyKSkgJT4lDQogICAgDQogICAgIyAtLS0gTGF5b3V0IEZpbmFsIEludGVncmFuZG8gZWwgQW7DoWxpc2lzIGRlIFNlc2dvIC0tLQ0KICAgIGxheW91dCgNCiAgICAgICAgIyBUw610dWxvOiBFbCB0w610dWxvIHByaW5jaXBhbCBzZSBtYW50aWVuZSBkZXNjcmlwdGl2by4NCiAgICAgICAgdGl0bGUgPSBsaXN0KHRleHQgPSAiVGVtcGVyYXR1cmEgbmFjaW9uYWw6IHVuIGNpY2xvIGVzdGFjaW9uYWwgY29uc3RhbnRlIGNvbiBhbHRhIGFtcGxpdHVkIGFudWFsPGJyPg0KICAgICAgICA8c3VwPjxiPkxhIG1lZGlhbmEgKFA1MCkgc2UgYWNlcmNhIGFsIGzDrW1pdGUgc3VwZXJpb3IgKFA5MCkgZW4gdmVyYW5vLCBpbmRpY2FuZG8gdW4gZnVlcnRlIHNlc2dvIGhhY2lhIGNvbmRpY2lvbmVzIGPDoWxpZGFzIGVuIGxhIG1heW9yw61hIGRlIGxhcyBlc3RhY2lvbmVzLjwvYj48L3N1cD4iKSwNCiAgICAgICAgDQogICAgICAgICMgRWplIFggDQogICAgICAgIHhheGlzID0gbGlzdCgNCiAgICAgICAgICAgIHRpdGxlID0gIkZlY2hhIiwNCiAgICAgICAgICAgIGR0aWNrID0gIk0xMiIsIA0KICAgICAgICAgICAgdGlja2Zvcm1hdCA9ICIlYiAlWSINCiAgICAgICAgKSwNCiAgICAgICAgDQogICAgICAgICMgRWplIFkNCiAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlPSJUZW1wZXJhdHVyYSBNZW5zdWFsICjCsEMpIiksIA0KICAgICAgICANCiAgICAgICAgIyBMZXllbmRhDQogICAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSBsaXN0KHRleHQgPSAnPGI+TcOpdHJpY2FzPC9iPicpLA0KICAgICAgICAgICAgICAgICAgICAgIHggPSAxMDAsDQogICAgICAgICAgICAgICAgICAgICAgeSA9IDEpLA0KICAgICAgICANCiAgICAgICAgIyBGdWVudGUNCiAgICAgICAgYW5ub3RhdGlvbnMgPSBsaXN0KA0KICAgICAgICAgICAgbGlzdCh0ZXh0ID0gIkZ1ZW50ZTogU01OIOKAkyBlbGFib3JhY2nDs24gcHJvcGlhLiIsDQogICAgICAgICAgICAgICAgIHNob3dhcnJvdyA9IEZBTFNFLCB4cmVmID0gInBhcGVyIiwgeXJlZiA9ICJwYXBlciIsIHggPSAxLCB5ID0gLTAuMTUsDQogICAgICAgICAgICAgICAgIHhhbmNob3IgPSAncmlnaHQnLCB5YW5jaG9yID0gJ2F1dG8nLCBmb250ID0gbGlzdChzaXplID0gMTApKQ0KICAgICAgICApLA0KICAgICAgICANCiAgICAgICAgbWFyZ2luID0gbGlzdChiID0gODAsIHQgPSA4MCkNCiAgICApDQoNCnBfcmliYm9uDQpgYGANCg0KDQojIyA5LjIuIE1vZGVsYWRvIHkgUHJvbsOzc3RpY28gKFByb3BoZXQpDQoNCkNvbW8gZXhwZXJpbWVudG8gZmluYWwsIGludGVudGFtb3MgcHJlZGVjaXIgZGF0b3MgbWV0ZW9yb2zDs2dpY29zIHVzYW5kbyBlbCBtb2RlbG8gcHJvcGhldCBkZSBGYWNlYm9vayAvIE1ldGEuDQoNClByaW1lcm8sIGludGVudGFtb3MgcHJvbm9zdGljYXIgbGEgcHJlY2lwaXRhY2nDs24gbWVuc3VhbCBwYXJhIGxhIGVzdGFjacOzbiBjb24gbcOhcyBkYXRvcyAoMTk5MS0yMDI0KS4gRW50cmVuYW1vcyBjb24gZGF0b3MgaGFzdGEgMjAxNyB5IHByb2JhbW9zIGVuIDIwMTgtYWRlbGFudGUuIEVsIG1vZGVsbyBjYXB0dXJhIGxhIGVzdGFjaW9uYWxpZGFkIGdlbmVyYWwuDQoNCmBgYHtyfQ0KIyAtLS0gUEFTTyAxOiBDYXJnYXIgeSBQcmVwYXJhciBsb3MgRGF0b3MgLS0tDQpkZl9sbHV2aWEgPC0gZGZfbGx1dmlhX2Nvbl9pbmZvDQoNCiMgTGltcGlhbW9zIGxvcyBkYXRvcw0KZGZfbGltcGlvIDwtIGRmX2xsdXZpYSAlPiUNCiAgbXV0YXRlKA0KICAgIGZlY2hhID0gYXMuRGF0ZShmZWNoYSksDQogICAgcHJlY2lwaXRhY2lvbiA9IGFzLm51bWVyaWMocHJlY2lwaXRhY2lvbikNCiAgKQ0KDQojIC0tLSBQQVNPIDI6IEVuY29udHJhciBsYSBNZWpvciBFc3RhY2nDs24gKGNvbiBtw6FzIGRhdG9zIHbDoWxpZG9zKSAtLS0NCmVzdGFjaW9uX2Nvbl9tYXNfZGF0b3MgPC0gZGZfbGltcGlvICU+JQ0KICBncm91cF9ieShub21icmUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgcmVnaXN0cm9zX3ZhbGlkb3MgPSBzdW0oIWlzLm5hKHByZWNpcGl0YWNpb24pKQ0KICApICU+JQ0KICAjIE9yZGVuYW1vcyBkZSBtYXlvciBhIG1lbm9yDQogIGFycmFuZ2UoZGVzYyhyZWdpc3Ryb3NfdmFsaWRvcykpICU+JQ0KICAjIEVsZWdpbW9zIGxhIHByaW1lcmEgKGxhIG1lam9yKQ0KICBzbGljZSgxKSAlPiUNCiAgcHVsbChub21icmUpICMgRXh0cmFlbW9zIGVsIG5vbWJyZQ0KDQpwcmludChwYXN0ZSgiRXN0YWNpw7NuIHNlbGVjY2lvbmFkYToiLCBlc3RhY2lvbl9jb25fbWFzX2RhdG9zKSkNCg0KIyAtLS0gUEFTTyAzOiBQcmVwYXJhciBEYXRvcyBwYXJhIFByb3BoZXQgKEZpbHRyYXIgeSBBZ3JlZ2FyKSAtLS0NCmRmX3Byb3BoZXQgPC0gZGZfbGltcGlvICU+JQ0KICBmaWx0ZXIobm9tYnJlID09IGVzdGFjaW9uX2Nvbl9tYXNfZGF0b3MpICU+JQ0KICBtdXRhdGUobWVzX2FubyA9IGZsb29yX2RhdGUoZmVjaGEsICJtb250aCIpKSAlPiUNCiAgZ3JvdXBfYnkobWVzX2FubykgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBwcmVjaXBpdGFjaW9uX3RvdGFsID0gc3VtKHByZWNpcGl0YWNpb24sIG5hLnJtID0gVFJVRSkNCiAgKSAlPiUNCiAgIyBSZW5vbWJyYW1vcyBwYXJhIHF1ZSBQcm9waGV0IGVudGllbmRhDQogIHJlbmFtZSgNCiAgICBkcyA9IG1lc19hbm8sDQogICAgeSA9IHByZWNpcGl0YWNpb25fdG90YWwNCiAgKSAlPiUNCiAgdW5ncm91cCgpDQoNCiMgLS0tIFBBU08gNDogRGl2aWRpciBEYXRvcyAoVHJhaW4geSBUZXN0KSAtLS0NCiMgVHJhaW46IDE5OTEtMDEtMDEgaGFzdGEgMjAxNy0xMi0zMQ0KIyBUZXN0OiAyMDE4LTAxLTAxIGhhc3RhIGVsIGZpbmFsDQoNCmRmX3RyYWluIDwtIGRmX3Byb3BoZXQgJT4lDQogIGZpbHRlcihkcyA8PSBhcy5EYXRlKCIyMDE3LTEyLTMxIikpDQoNCmRmX3Rlc3QgPC0gZGZfcHJvcGhldCAlPiUNCiAgZmlsdGVyKGRzID4gYXMuRGF0ZSgiMjAxNy0xMi0zMSIpKQ0KDQpwcmludChwYXN0ZSgiUmVnaXN0cm9zIGRlIEVudHJlbmFtaWVudG86IiwgbnJvdyhkZl90cmFpbikpKQ0KcHJpbnQocGFzdGUoIlJlZ2lzdHJvcyBkZSBQcnVlYmE6IiwgbnJvdyhkZl90ZXN0KSkpDQoNCiMgLS0tIFBBU08gNTogQ3JlYXIgeSBFbnRyZW5hciBlbCBNb2RlbG8gUHJvcGhldCAtLS0NCg0KIyBJbmljaWFtb3MgUHJvcGhldC4NCiMgUHJvcGhldCBkZXRlY3RhcsOhIGF1dG9tw6F0aWNhbWVudGUgbGEgZXN0YWNpb25hbGlkYWQgYW51YWwgKHllYXJseS5zZWFzb25hbGl0eSkNCiMgeWEgcXVlIHRlbmVtb3MgZGF0b3MgZGUgdmFyaW9zIGHDsW9zLg0KbW9kZWwgPC0gcHJvcGhldCgpDQoNCiMgQWp1c3RhbW9zIGVsIG1vZGVsbyAoZW50cmVuYW1vcykNCm1vZGVsIDwtIGZpdC5wcm9waGV0KG1vZGVsLCBkZl90cmFpbikNCg0KIyAtLS0gUEFTTyA2OiBQcmVkZWNpciB5IEV2YWx1YXIgLS0tDQpmdXR1cmVfdGVzdCA8LSBkZl90ZXN0ICU+JSBzZWxlY3QoZHMpDQoNCiMgUmVhbGl6YW1vcyBsYSBwcmVkaWNjacOzbg0KZm9yZWNhc3QgPC0gcHJlZGljdChtb2RlbCwgZnV0dXJlX3Rlc3QpDQoNCiMgVW5pbW9zIGxhIHByZWRpY2Npw7NuIChmb3JlY2FzdCkgY29uIGxvcyB2YWxvcmVzIHJlYWxlcyAoZGZfdGVzdCkNCiMgcGFyYSBwb2RlciBjb21wYXJhcg0KZGZfY29tcGFyYWNpb24gPC0gZm9yZWNhc3QgJT4lDQogIHNlbGVjdChkcywgeWhhdCwgeWhhdF9sb3dlciwgeWhhdF91cHBlcikgJT4lDQogIGxlZnRfam9pbihkZl90ZXN0LCBieSA9ICJkcyIpDQoNCiMgLS0tIFBBU08gNzogVmlzdWFsaXphciBsb3MgUmVzdWx0YWRvcyAtLS0NCg0KIyAxLiBHcsOhZmljbyBjb21wbGV0byBkZSBQcm9waGV0IChQcmVkaWNjacOzbiB2cyBSZWFsKQ0KIyBFc3RvIGNyZWFyw6EgdW4gZ3LDoWZpY28gaW50ZXJhY3Rpdm8gc2kgZXN0w6FzIGVuIFJTdHVkaW8NCnBsb3RfZm9yZWNhc3QgPC0gcGxvdChtb2RlbCwgZm9yZWNhc3QpDQpwbG90X2ZvcmVjYXN0IDwtIHBsb3RseTo6Z2dwbG90bHkocGxvdF9mb3JlY2FzdCkgIyA8LS0gQcORQURFIEVTVEEgTMONTkVBDQpwbG90X2ZvcmVjYXN0DQoNCiMgMi4gR3LDoWZpY28gZGUgbG9zIGNvbXBvbmVudGVzIGRlbCBtb2RlbG8NCiMgRXN0byB0ZSBtb3N0cmFyw6EgbGEgdGVuZGVuY2lhLCBsYSBlc3RhY2lvbmFsaWRhZCBhbnVhbCwgZXRjLg0KcGxvdF9mb3JlY2FzdF9jb21wDQpgYGANCg0KUmVwZXRpbW9zIGVsIHByb2Nlc28gcGFyYSBsYSB0ZW1wZXJhdHVyYSBkaWFyaWEsIHF1ZSBlcyB1bmEgc2XDsWFsIG11Y2hvIG3DoXMgZXN0YWJsZS4gRW50cmVuYW1vcyBlbCBtb2RlbG8gZW4gbGEgZXN0YWNpw7NuIGNvbiBtw6FzIGRhdG9zIGRlIHRlbXBlcmF0dXJhIGhhc3RhIGZpbmFsZXMgZGUgMjAyMyB5IGxlIHBlZGltb3MgcXVlIHByZWRpZ2EgZWwgZnV0dXJvIChlbCAidGVzdCBzZXQiKS4NCmBgYHtyfQ0KIyAtLS0gUEFTTyAyOiBFbmNvbnRyYXIgbGEgTWVqb3IgRXN0YWNpw7NuIChjb24gbcOhcyBkYXRvcyBkZSBURU1QRVJBVFVSQSkgLS0tDQplc3RhY2lvbl9jb25fbWFzX2RhdG9zX3RlbXAgPC0gZGYgJT4lDQogIGZpbHRlcighaXMubmEodGVtcCkpICU+JSAjIEZpbHRyYW1vcyBOQXMgZGUgdGVtcGVyYXR1cmENCiAgZ3JvdXBfYnkobm9tYnJlKSAlPiUNCiAgc3VtbWFyaXNlKHJlZ2lzdHJvc192YWxpZG9zID0gbigpKSAlPiUgIyBDb250YW1vcyBsYXMgZmlsYXMgcmVzdGFudGVzDQogIGFycmFuZ2UoZGVzYyhyZWdpc3Ryb3NfdmFsaWRvcykpICU+JQ0KICBzbGljZSgxKSAlPiUNCiAgcHVsbChub21icmUpIA0KDQpwcmludChwYXN0ZSgiRXN0YWNpw7NuIHNlbGVjY2lvbmFkYSAoY29uIG3DoXMgZGF0b3MgZGUgdGVtcCk6IiwgZXN0YWNpb25fY29uX21hc19kYXRvc190ZW1wKSkNCg0KIyAtLS0gUEFTTyAzOiBQcmVwYXJhciBEYXRvcyBwYXJhIFByb3BoZXQgKEFHUkVHQUNJw5NOIERJQVJJQSkgLS0tDQpkZl9wcm9waGV0X2RpYXJpbyA8LSBkZiAlPiUNCiAgZmlsdGVyKG5vbWJyZSA9PSBlc3RhY2lvbl9jb25fbWFzX2RhdG9zX3RlbXApICU+JQ0KICBncm91cF9ieShmZWNoYSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICB0ZW1wX21lZGlhX2RpYXJpYSA9IG1lYW4odGVtcCwgbmEucm0gPSBUUlVFKQ0KICApICU+JQ0KICByZW5hbWUoDQogICAgZHMgPSBmZWNoYSwNCiAgICB5ID0gdGVtcF9tZWRpYV9kaWFyaWENCiAgKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBkaXN0aW5jdCgpICMgTm9zIGFzZWd1cmFtb3MgZGUgdGVuZXIgdW4gc29sbyB2YWxvciBwb3IgZMOtYQ0KDQojIC0tLSBQQVNPIDQ6IERpdmlkaXIgRGF0b3MgKFRyYWluIHkgVGVzdCkgLS0tDQpmZWNoYV9jb3J0ZSA8LSBhcy5EYXRlKCIyMDIzLTEwLTA3IikNCg0KZGZfdHJhaW5fZGlhcmlvIDwtIGRmX3Byb3BoZXRfZGlhcmlvICU+JQ0KICBmaWx0ZXIoZHMgPD0gZmVjaGFfY29ydGUpDQoNCmRmX3Rlc3RfZGlhcmlvIDwtIGRmX3Byb3BoZXRfZGlhcmlvICU+JQ0KICBmaWx0ZXIoZHMgPiBmZWNoYV9jb3J0ZSkNCg0KcHJpbnQocGFzdGUoIlJlZ2lzdHJvcyBEaWFyaW9zIGRlIEVudHJlbmFtaWVudG86IiwgbnJvdyhkZl90cmFpbl9kaWFyaW8pKSkNCnByaW50KHBhc3RlKCJSZWdpc3Ryb3MgRGlhcmlvcyBkZSBQcnVlYmE6IiwgbnJvdyhkZl90ZXN0X2RpYXJpbykpKQ0KDQojIC0tLSBQQVNPIDU6IENyZWFyIHkgRW50cmVuYXIgZWwgTW9kZWxvIFByb3BoZXQgLS0tDQojIFByb3BoZXQgZGV0ZWN0YXLDoSBhdXRvbcOhdGljYW1lbnRlIGxhIGVzdGFjaW9uYWxpZGFkIEFOVUFMIHkgU0VNQU5BTA0KbW9kZWxfdGVtcF9kaWFyaW8gPC0gcHJvcGhldCh3ZWVrbHkuc2Vhc29uYWxpdHkgPSBGQUxTRSkNCg0KIyBBanVzdGFtb3MgZWwgbW9kZWxvIChlbnRyZW5hbW9zKQ0KbW9kZWxfdGVtcF9kaWFyaW8gPC0gZml0LnByb3BoZXQobW9kZWxfdGVtcF9kaWFyaW8sIGRmX3RyYWluX2RpYXJpbykNCg0KIyAtLS0gUEFTTyA2OiBQcmVkZWNpciB5IEV2YWx1YXIgLS0tDQoNCiMgQ3JlYW1vcyB1biBkYXRhZnJhbWUgImZ1dHVybyINCmZ1dHVyZV90ZXN0X2RpYXJpbyA8LSBkZl90ZXN0X2RpYXJpbyAlPiUgc2VsZWN0KGRzKQ0KDQojIFJlYWxpemFtb3MgbGEgcHJlZGljY2nDs24NCmZvcmVjYXN0X3RlbXBfZGlhcmlvIDwtIHByZWRpY3QobW9kZWxfdGVtcF9kaWFyaW8sIGZ1dHVyZV90ZXN0X2RpYXJpbykNCg0KIyAtLS0gUEFTTyA3OiBWaXN1YWxpemFyIGxvcyBSZXN1bHRhZG9zIC0tLQ0KDQojIDEuIEdyw6FmaWNvIGNvbXBsZXRvIGRlIFByb3BoZXQgKFByZWRpY2Npw7NuIHZzIFJlYWwpDQojIEVzdG8gdGFyZGFyw6EgdW4gcG9xdWl0byBtw6FzIGVuIGdlbmVyYXJzZSwgwqF0aWVuZSBtdWNob3MgcHVudG9zIQ0KcF9tYWluIDwtIHBsb3QobW9kZWxfdGVtcF9kaWFyaW8sIGZvcmVjYXN0X3RlbXBfZGlhcmlvKQ0KcF9tYWluIDwtIHBsb3RseTo6Z2dwbG90bHkocF9tYWluKQ0KcF9tYWluDQojIDIuIEdyw6FmaWNvIGRlIGxvcyBjb21wb25lbnRlcyBkZWwgbW9kZWxvDQojIEFob3JhIHZlcsOhcyB1bmEgZXN0YWNpb25hbGlkYWQgInNlbWFuYWwiICh3ZWVrbHkpIGFkZW3DoXMgZGUgbGEgYW51YWwNCnBfY29tcA0KYGBgDQoNCkVsIGdyw6FmaWNvIGRlIGNvbXBvbmVudGVzIChwX2NvbXApIGRlc2NvbXBvbmUgZWwgcHJvbsOzc3RpY28gZGVsIG1vZGVsbywgbW9zdHJhbmRvIGxhIGZ1ZXJ0ZSB0ZW5kZW5jaWEgYW51YWwgeSBsYSB0ZW5kZW5jaWEgc2VtYW5hbCAocGF0cm9uZXMgZGUgZMOtYXMgZGUgbGEgc2VtYW5hKS4NCg0KRWwgZ3LDoWZpY28gZGUgY29tcGFyYWNpw7NuIChwcl9jb21wKSBtdWVzdHJhIHF1ZSBsYSBwcmVkaWNjacOzbiBkZWwgbW9kZWxvIChhenVsKSBzaWd1ZSBtdXkgZGUgY2VyY2EgYSBsb3MgdmFsb3JlcyByZWFsZXMgKG5lZ3JvKSBlbiBlbCBjb25qdW50byBkZSBwcnVlYmEuDQoNCmBgYHtyfQ0KIyAtLS0gUEFTTyA4OiBDb21wYXJhciBQcmVkaWNjacOzbiBEaWFyaWEgdnMuIFJlYWxpZGFkIChHcsOhZmljbyBGaW5hbCkgLS0tDQoNCiMgMS4gVW5pbW9zIGxhcyBwcmVkaWNjaW9uZXMgY29uIGxvcyBkYXRvcyByZWFsZXMgZGVsIFRlc3QgU2V0IChEaWFyaW8pDQpkZl9jb21wYXJhY2lvbl9kaWFyaW8gPC0gZm9yZWNhc3RfdGVtcF9kaWFyaW8gJT4lDQogIHNlbGVjdChkcywgeWhhdCwgeWhhdF9sb3dlciwgeWhhdF91cHBlcikgJT4lDQogIGxlZnRfam9pbihkZl90ZXN0X2RpYXJpbywgYnkgPSAiZHMiKSAjICdkZl90ZXN0X2RpYXJpbycgdGllbmUgZWwgdmFsb3IgcmVhbCAneScNCg0KIyAyLiBHcmFmaWNhbW9zIGxhIGNvbXBhcmFjacOzbiB1c2FuZG8gZ2dwbG90Mg0KIyAoQXNlZ8O6cmF0ZSBkZSB0ZW5lciAnbGlicmFyeShnZ3Bsb3QyKScgY2FyZ2FkYSBhbCBpbmljaW8gZGUgdHUgUm1kKQ0KDQpwcl9jb21wIDwtIGdncGxvdChkZl9jb21wYXJhY2lvbl9kaWFyaW8sIGFlcyh4ID0gZHMpKSArDQogIA0KICAjIEzDrW5lYSBOZWdyYTogTG9zIHZhbG9yZXMgUkVBTEVTIGRpYXJpb3MgcXVlIGd1YXJkYW1vcw0KICBnZW9tX2xpbmUoYWVzKHkgPSB5LCBjb2xvciA9ICJWYWxvciBSZWFsIiksIGxpbmV3aWR0aCA9IDAuOCkgKw0KICANCiAgIyBMw61uZWEgQXp1bCBQdW50ZWFkYTogTGEgUFJFRElDQ0nDk04gZGlhcmlhIGRlbCBtb2RlbG8NCiAgZ2VvbV9saW5lKGFlcyh5ID0geWhhdCwgY29sb3IgPSAiUHJlZGljY2nDs24iKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgbGluZXdpZHRoID0gMSkgKw0KICANCiAgIyDDgXJlYSBHcmlzIGRlIEluY2VydGlkdW1icmUNCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSB5aGF0X2xvd2VyLCB5bWF4ID0geWhhdF91cHBlciksIGZpbGwgPSAiZ3JleSIsIGFscGhhID0gMC4zKSArDQogIA0KICAjIFTDrXR1bG9zIHkgY29sb3Jlcw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiVmFsb3IgUmVhbCIgPSAiYmxhY2siLCAiUHJlZGljY2nDs24iID0gImRvZGdlcmJsdWUiKSkgKw0KICBsYWJzKHRpdGxlID0gIkNvbXBhcmFjacOzbjogVGVtcGVyYXR1cmEgRGlhcmlhIFJlYWwgdnMuIFByZWRpY2Npw7NuIChUZXN0IFNldCkiLA0KICAgICAgIHN1YnRpdGxlID0gIkVsIG1vZGVsbyBzaWd1ZSBiaWVuIGxhIHRlbmRlbmNpYSBnZW5lcmFsIHkgbGEgZXN0YWNpb25hbGlkYWQuIiwNCiAgICAgICB5ID0gIlRlbXBlcmF0dXJhIE1lZGlhIERpYXJpYSAowrBDKSIsDQogICAgICAgeCA9ICJGZWNoYSIsDQogICAgICAgY29sb3IgPSAiTGV5ZW5kYSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpwcl9jb21wIDwtIHBsb3RseTo6Z2dwbG90bHkocHJfY29tcCkNCnByX2NvbXANCmBgYA0KDQpDYWxjdWxhbW9zIGVsIGVycm9yIG51bcOpcmljbzoNCi0gTUFFIChFcnJvciBBYnNvbHV0byBNZWRpbyk6IEVuIHByb21lZGlvLCBlbCBtb2RlbG8gc2UgZXF1aXZvY2EgcG9yIGVzdGEgY2FudGlkYWQgZGUgZ3JhZG9zLg0KLSBSTVNFIChSYcOteiBkZWwgRXJyb3IgQ3VhZHLDoXRpY28gTWVkaW8pOiBTaW1pbGFyIGFsIE1BRSwgcGVybyBwZW5hbGl6YSBtw6FzIGxvcyBlcnJvcmVzIGdyYW5kZXMuDQoNCmBgYHtyfQ0KIyAtLS0gUEFTTyA5OiBFdmFsdWFjacOzbiBOdW3DqXJpY2EgKE1BRSB5IFJNU0UpIC0tLQ0KDQojIDEuIENhbGN1bGFtb3MgZWwgZXJyb3IgZGUgY2FkYSBkw61hDQpkZl9lcnJvcmVzIDwtIGRmX2NvbXBhcmFjaW9uX2RpYXJpbyAlPiUNCiAgbXV0YXRlKA0KICAgIGVycm9yID0geSAtIHloYXQsICAgICAgICAgICAjIEVycm9yIChkaWZlcmVuY2lhIHNpbXBsZSkNCiAgICBlcnJvcl9hYnNvbHV0byA9IGFicyhlcnJvcikgIyBFcnJvciBhYnNvbHV0byAoc2llbXByZSBwb3NpdGl2bykNCiAgKQ0KDQojIDIuIENhbGN1bGFtb3MgZWwgTUFFIChNZWFuIEFic29sdXRlIEVycm9yKQ0KIyBFcyBlbCBwcm9tZWRpbyBkZSBsb3MgZXJyb3JlcyBhYnNvbHV0b3MNCm1hZSA8LSBtZWFuKGRmX2Vycm9yZXMkZXJyb3JfYWJzb2x1dG8sIG5hLnJtID0gVFJVRSkNCg0KcHJpbnQocGFzdGUoIk1BRSAoRXJyb3IgQWJzb2x1dG8gTWVkaW8pOiIsIHJvdW5kKG1hZSwgMiksICJncmFkb3MiKSkNCnByaW50KCJFc3RvIHNpZ25pZmljYSBxdWUsIGVuIHByb21lZGlvLCBlbCBtb2RlbG8gc2UgZXF1aXZvY8OzIHBvciBlc3RhIGNhbnRpZGFkIGRlIGdyYWRvcy4iKQ0KDQojIDMuIENhbGN1bGFtb3MgZWwgUk1TRSAoUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IpDQojIEVzIGxhIHJhw616IGN1YWRyYWRhIGRlbCBwcm9tZWRpbyBkZSBsb3MgZXJyb3JlcyBhbCBjdWFkcmFkbw0Kcm1zZSA8LSBzcXJ0KG1lYW4oZGZfZXJyb3JlcyRlcnJvcl4yLCBuYS5ybSA9IFRSVUUpKQ0KDQpwcmludChwYXN0ZSgiUk1TRSAoUmHDrXogZGVsIEVycm9yIEN1YWRyw6F0aWNvIE1lZGlvKToiLCByb3VuZChybXNlLCAyKSwgImdyYWRvcyIpKQ0KcHJpbnQoIlNpbWlsYXIgYWwgTUFFLCBwZXJvIHBlbmFsaXphIG3DoXMgbG9zIGVycm9yZXMgZ3JhbmRlcy4iKQ0KYGBgDQoNCkZpbmFsbWVudGUsIGFuYWxpemFtb3MgbG9zIHJlc2lkdW9zIChsb3MgZXJyb3JlcyBkZWwgbW9kZWxvKS4gRWwgZ3LDoWZpY28gZGUgcmVzaWR1b3MgZW4gZWwgdGllbXBvIChwX3JlcykgZGViZSBwYXJlY2VyIHJ1aWRvIGFsZWF0b3JpbyBjZW50cmFkbyBlbiAwLg0KYGBge3J9DQojIC0tLSBQQVNPIDEwIChBKTogR3LDoWZpY28gZGUgUmVzaWR1b3MgYSBsbyBsYXJnbyBkZWwgdGllbXBvIC0tLQ0KDQojIGRmX2Vycm9yZXMgeWEgdGllbmUgbGEgY29sdW1uYSAnZXJyb3InICh5IC0geWhhdCkgeSAnZHMnIChmZWNoYSkNCnBfcmVzIDwtIGdncGxvdChkZl9lcnJvcmVzLCBhZXMoeCA9IGRzLCB5ID0gZXJyb3IpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJyZWQiLCBhbHBoYSA9IDAuOCkgKw0KICANCiAgIyBMw61uZWEgZGUgcmVmZXJlbmNpYSBlbiAwIChlbCBlcnJvciBwZXJmZWN0bykNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiYmx1ZSIsIGxpbmV3aWR0aCA9IDEpICsNCiAgDQogIGxhYnModGl0bGUgPSAiUmVzaWR1b3MgKEVycm9yZXMpIGEgbG8gbGFyZ28gZGVsIHRpZW1wbyIsDQogICAgICAgc3VidGl0bGUgPSAiSWRlYWxtZW50ZTogUnVpZG8gYWxlYXRvcmlvIGNlbnRyYWRvIGVuIDAiLA0KICAgICAgIHkgPSAiRXJyb3IgKFJlYWwgLSBQcmVkaWNjacOzbiBlbiDCsEMpIiwNCiAgICAgICB4ID0gIkZlY2hhIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCnBfcmVzIDwtIHBsb3RseTo6Z2dwbG90bHkocF9yZXMpDQpwX3Jlcw0KYGBgDQpgYGB7cn0NCiMgLS0tIFBBU08gMTAgKEIpOiBIaXN0b2dyYW1hIGRlIFJlc2lkdW9zIC0tLQ0KDQpoaXN0X3JlcyA8LSBnZ3Bsb3QoZGZfZXJyb3JlcywgYWVzKHggPSBlcnJvcikpICsNCiAgDQogICMgSGlzdG9ncmFtYSBjb24gdW4gY29sb3IgZGUgcmVsbGVubyBtw6FzIGFncmFkYWJsZQ0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgDQogICAgICAgICAgICAgICAgIGJpbnMgPSA0MCwgIyBBdW1lbnRhbW9zIHVuIHBvY28gbG9zIGJpbnMNCiAgICAgICAgICAgICAgICAgZmlsbCA9ICJkZWVwc2t5Ymx1ZTMiLCANCiAgICAgICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCAjIEJvcmRlIGJsYW5jbyBwYXJhIHNlcGFyYXIgbGFzIGJhcnJhcw0KICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuOCkgKw0KICANCiAgIyBDdXJ2YSBkZSBkZW5zaWRhZCBwYXJhIHZlciBsYSBmb3JtYSBnZW5lcmFsDQogIGdlb21fZGVuc2l0eShjb2xvciA9ICJibGFjayIsIGxpbmV3aWR0aCA9IDEuMiwgYWxwaGEgPSAwLjkpICsNCiAgDQogICMgTMOtbmVhIHZlcnRpY2FsIGVuIENlcm8gKEVycm9yIFBlcmZlY3RvKQ0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCANCiAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsIA0KICAgICAgICAgICAgIGxpbmV0eXBlID0gImRhc2hlZCIsIA0KICAgICAgICAgICAgIGxpbmV3aWR0aCA9IDEuMikgKw0KICANCiAgIyBMw61uZWFzIHZlcnRpY2FsZXMgcGFyYSBlbCBFcnJvciBBYnNvbHV0byBNZWRpbyAoTUFFKQ0KICAjIEVzdG8gbm9zIGRhIHVuIGNvbnRleHRvIGRlIHF1w6kgdGFuIGdyYW5kZSBlcyBlbCBlcnJvciBwcm9tZWRpbw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBtYWUsIA0KICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsIA0KICAgICAgICAgICAgIGxpbmV0eXBlID0gImRvdHRlZCIsIA0KICAgICAgICAgICAgIGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLW1hZSwgDQogICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgDQogICAgICAgICAgICAgbGluZXR5cGUgPSAiZG90dGVkIiwgDQogICAgICAgICAgICAgbGluZXdpZHRoID0gMSkgKw0KICANCiAgIyBUw610dWxvcyB5IGV0aXF1ZXRhcyBtZWpvcmFkb3MNCiAgbGFicygNCiAgICB0aXRsZSA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgZGUgUHJlZGljY2nDs24iLA0KICAgIHN1YnRpdGxlID0gcGFzdGUwKCJMYSBtYXlvcsOtYSBkZSBsb3MgZXJyb3JlcyBzZSBjZW50cmFuIGVuIDAuXG5MYXMgbMOtbmVhcyByb2phcyBwdW50ZWFkYXMgbWFyY2FuIGVsIEVycm9yIEFic29sdXRvIE1lZGlvIChNQUU6ICsvLSAiLCByb3VuZChtYWUsIDIpLCAiwrApIiksDQogICAgeCA9ICJFcnJvciBkZSBQcmVkaWNjacOzbiAoR3JhZG9zIMKwQykiLA0KICAgIHkgPSAiRGVuc2lkYWQiDQogICkgKw0KICANCiAgIyBVbiB0ZW1hIGxpbXBpbw0KICB0aGVtZV9taW5pbWFsKCkNCmhpc3RfcmVzIDwtIHBsb3RseTo6Z2dwbG90bHkoaGlzdF9yZXMpDQpoaXN0X3Jlcw0KYGBgDQpFbCBoaXN0b2dyYW1hIGRlIHJlc2lkdW9zIChoaXN0X3JlcykgbXVlc3RyYSBxdWUgbGEgZ3JhbiBtYXlvcsOtYSBkZSBsb3MgZXJyb3JlcyBzb24gbXV5IGNlcmNhbm9zIGEgY2VybywgY29uIGFsZ3Vub3MgZXJyb3JlcyBtw6FzIGdyYW5kZXMgZW4gbGFzIGNvbGFzLiBFc3RvIGNvbmZpcm1hIHVuIGJ1ZW4gYWp1c3RlIGRlbCBtb2RlbG8uDQoNCiMgMTAuIFNhbGlkYSBkZSBEYXRvcyB5IEdyw6FmaWNvcw0KIyMgRGVzY2FyZ2EgZGUgZGF0YWZyYW1lcyBwYXJhIGNvbnN0cnVpciBncmFmaWNvcyBlbiBsYSBzaGlueSBhcHANCkVuIGVzdGUgYmxvcXVlIGZpbmFsLCBwcmVwYXJhbW9zIGxvcyBkYXRvcyBwYXJhIHNlciBjb25zdW1pZG9zIHBvciB1bmEgYXBsaWNhY2nDs24gZXh0ZXJuYSAoY29tbyB1bmEgU2hpbnkgYXBwKS4gTm9ybWFsaXphbW9zIGxvcyBkYXRhZnJhbWVzIGNsYXZlIChjb21vIGZlYXRfbW9udGgsIGRhaWx5X3N0YXRpb24pIHkgbG9zIGd1YXJkYW1vcyBlbiBmb3JtYXRvIC5SZHMuDQpgYGB7cn0NCiMgLS0tIDApIE5vcm1hbGl6YWNpw7NuIGRlIGRmIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBkZiB5YSBleGlzdGUgZW4gbWVtb3JpYQ0KZGYgPC0gYXMuZGF0YS50YWJsZShkZikNCnNldG5hbWVzKGRmLCBtYWtlX2NsZWFuX25hbWVzKG5hbWVzKGRmKSkpDQoNCiMgVGlwb3MgKHNpbiA6PSwgdXNhbmRvIHNldCgpKQ0KIyBmZWNoYQ0KZGZbLCBmZWNoYV9jaHIgOj0gYXMuY2hhcmFjdGVyKGZlY2hhKV0NCnNldChkZiwgaiA9ICJmZWNoYSIsIHZhbHVlID0gYXMuRGF0ZShkZltbImZlY2hhX2NociJdXSkpDQpkZlssIGZlY2hhX2NociA6PSBOVUxMXQ0KDQojIGhvcmEsIG5ybw0Kc2V0KGRmLCBqID0gImhvcmEiLCB2YWx1ZSA9IGFzLmludGVnZXIoZGZbWyJob3JhIl1dKSkNCnNldChkZiwgaiA9ICJucm8iLCAgdmFsdWUgPSBhcy5pbnRlZ2VyKGRmW1sibnJvIl1dKSkNCg0KIyBsYXQvbG9uZw0Kc2V0KGRmLCBqID0gImxhdGl0dWQiLCAgdmFsdWUgPSBhcy5udW1lcmljKGRmW1sibGF0aXR1ZCJdXSkpDQpzZXQoZGYsIGogPSAibG9uZ2l0dWQiLCB2YWx1ZSA9IGFzLm51bWVyaWMoZGZbWyJsb25naXR1ZCJdXSkpDQoNCiMgZGF0ZXRpbWUgbG9jYWwgQkENCnNldChkZiwgaiA9ICJkYXRldGltZSIsDQogICAgdmFsdWUgPSBhcy5QT1NJWGN0KHBhc3RlKGRmW1siZmVjaGEiXV0sIHNwcmludGYoIiUwMmQ6MDA6MDAiLCBkZltbImhvcmEiXV0pKSwNCiAgICAgICAgICAgICAgICAgICAgICAgdHogPSAiQW1lcmljYS9BcmdlbnRpbmEvQnVlbm9zX0FpcmVzIikpDQoNCi5zYXZlX3JkcyhkZiwgImRhdGEvZGZfY2xlYW4iKQ0KLnNhdmVfcmRzKGRhaWx5X3N0YXRpb24sICJkYXRhL2RhaWx5X3N0YXRpb24iKQ0KLnNhdmVfcmRzKG1vbnRobHlfYnlfc3RhdGlvbiwgImRhdGEvbW9udGhseV9ieV9zdGF0aW9uIikNCg0KIyAtLS0gMSkgR0REIG1lbnN1YWwgKGJhc2UgMTDCsEMpIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmdkZF9kYWlseSA8LSBkZlshaXMubmEodGVtcCksDQogICAgICAgICAgICAgICAgLihnZGQgPSBzdW0ocG1heCgwLCB0ZW1wIC0gMTApKS8yNCksDQogICAgICAgICAgICAgICAgYnkgPSAuKG5ybywgeW1kID0gYXMuRGF0ZShkYXRldGltZSkpXQ0KZ2RkX21vbnRoIDwtIGdkZF9kYWlseVssIC4oZ2RkX20gPSBzdW0oZ2RkKSksDQogICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gLihucm8sIHltID0gYXMuRGF0ZShmb3JtYXQoeW1kLCAiJVktJW0tMDEiKSkpXQ0KDQouc2F2ZV9yZHMoZ2RkX21vbnRoLCAiZGF0YS9nZGRfbW9udGgiKQ0KDQojIC0tLSAyKSBKb2luIGRlIGVzdGFjaW9uZXMgKE5ybyAtPiBOb21icmUpIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoew0KICBsaWJyYXJ5KGRhdGEudGFibGUpOyBsaWJyYXJ5KGphbml0b3IpDQp9KQ0KDQojIDIuMSBDYXJnYXIgQ1NWIHkgbm9ybWFsaXphciBub21icmVzDQplc3RfY3N2IDwtIGZyZWFkKCJzbW5fZXN0YWNpb25lcy5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIpDQpzZXRuYW1lcyhlc3RfY3N2LCBtYWtlX2NsZWFuX25hbWVzKG5hbWVzKGVzdF9jc3YpKSkgICMgJ05ybyctPiducm8nLCAnTm9tYnJlJy0+J25vbWJyZScNCg0KIyAyLjIgVGlwb3MgeSB2YWxpZGFjaW9uZXMNCmlmICghIm5ybyIgJWluJSBuYW1lcyhlc3RfY3N2KSkgc3RvcCgiRWwgQ1NWIG5vIHRpZW5lIGNvbHVtbmEgJ05ybycgKG8gJ25ybycgdHJhcyBub3JtYWxpemFyKS4iKQ0KaWYgKCEibm9tYnJlIiAlaW4lIG5hbWVzKGVzdF9jc3YpKSBzdG9wKCJFbCBDU1Ygbm8gdGllbmUgY29sdW1uYSAnTm9tYnJlJyAobyAnbm9tYnJlJyB0cmFzIG5vcm1hbGl6YXIpLiIpDQplc3RfY3N2WywgbnJvIDo9IGFzLmludGVnZXIobnJvKV0NCmVzdF9jc3ZbLCBub21icmUgOj0gYXMuY2hhcmFjdGVyKG5vbWJyZSldDQoNCiMgMi4zIFJlc29sdmVyIGR1cGxpY2Fkb3MgcG9yIG5ybzogcXVlZGFybm9zIGNvbiBlbCBub21icmUgbcOhcyBmcmVjdWVudGUNCmVzdF9tYXAgPC0gZXN0X2NzdlshaXMubmEobnJvKSAmICFpcy5uYShub21icmUpLA0KICAgICAgICAgICAgICAgICAgIC5OLCBieSA9IC4obnJvLCBub21icmUpXVtvcmRlcihucm8sIC1OKV1bLCAuU0RbMV0sIGJ5ID0gbnJvXVssIE4gOj0gTlVMTF0NCg0KIyAyLjQgQXNlZ3VyYXIgdGlwb3MgZW4gZ2RkX21vbnRoIHkgYWdyZWdhciBub21icmVfcmVmDQpzdG9waWZub3QoZXhpc3RzKCJnZGRfbW9udGgiKSkNCnNldG5hbWVzKGdkZF9tb250aCwgbWFrZV9jbGVhbl9uYW1lcyhuYW1lcyhnZGRfbW9udGgpKSkNCmlmICghIm5ybyIgJWluJSBuYW1lcyhnZGRfbW9udGgpKSBzdG9wKCJnZGRfbW9udGggbm8gdGllbmUgY29sdW1uYSAnbnJvJy4iKSAgIyBjb3JyZWdpZG8gZWwgdHlwbw0KZ2RkX21vbnRoWywgbnJvIDo9IGFzLmludGVnZXIobnJvKV0NCg0KZ2RkX21vbnRoX2pvaW5lZCA8LSBtZXJnZSgNCiAgZ2RkX21vbnRoLA0KICBlc3RfbWFwWywgLihucm8sIG5vbWJyZV9yZWYgPSBub21icmUpXSwNCiAgYnkgPSAibnJvIiwgYWxsLnggPSBUUlVFDQopDQoNCiMgLS0tIDMpIFByZXBhcmFyIG1vbnRobHlfYnlfc3RhdGlvbiAodGllbmUgbm9tYnJlLCBkcywgeSkgLS0tLS0tLS0tLS0tLS0tLS0tLS0NCm1vbnRobHlfYnlfc3RhdGlvbiA8LSBhcy5kYXRhLnRhYmxlKG1vbnRobHlfYnlfc3RhdGlvbikNCnNldG5hbWVzKG1vbnRobHlfYnlfc3RhdGlvbiwgbWFrZV9jbGVhbl9uYW1lcyhuYW1lcyhtb250aGx5X2J5X3N0YXRpb24pKSkgICMgbm9tYnJlLCBkcywgeQ0KDQojIFZhbGlkYWNpb25lcyBtw61uaW1hcw0KaWYgKCFhbGwoYygibm9tYnJlIiwiZHMiKSAlaW4lIG5hbWVzKG1vbnRobHlfYnlfc3RhdGlvbikpKSB7DQogIHN0b3AoIm1vbnRobHlfYnlfc3RhdGlvbiBkZWJlIHRlbmVyIGNvbHVtbmFzICdub21icmUnIHkgJ2RzJy4iKQ0KfQ0KbW9udGhseV9ieV9zdGF0aW9uWywgbm9tYnJlIDo9IGFzLmNoYXJhY3Rlcihub21icmUpXQ0KbW9udGhseV9ieV9zdGF0aW9uWywgZHMgOj0gYXMuY2hhcmFjdGVyKGRzKV0NCg0KIyBkcyBwdWVkZSB2ZW5pciBjb21vICJZWVlZLU1NIiBvICJZWVlZLU1NLUREIjogbm9ybWFsaXpvIHkgY3JlbyB5bSA9IHByaW1lciBkw61hIGRlIG1lcw0KbW9udGhseV9ieV9zdGF0aW9uWywgZHMgOj0gaWZlbHNlKGdyZXBsKCJeXFxkezR9LVxcZHsyfSQiLCBkcyksIHBhc3RlMChkcywgIi0wMSIpLCBkcyldDQptb250aGx5X2J5X3N0YXRpb25bLCB5bSA6PSBhcy5EYXRlKGRzKV0NCm1vbnRobHlfYnlfc3RhdGlvblssIHltIDo9IGFzLkRhdGUoZm9ybWF0KHltLCAiJVktJW0tMDEiKSldDQoNCiMgLS0tIDQpIEFncmVnYXIgR0REIHBvciBub21icmUgKyBtZXMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiMgTm9ybWFsaXpvIGdkZF9tb250aF9qb2luZWQgeSBhZ3JlZ28gcG9yIG5vbWJyZV9yZWYgKyB5bQ0Kc2V0bmFtZXMoZ2RkX21vbnRoX2pvaW5lZCwgbWFrZV9jbGVhbl9uYW1lcyhuYW1lcyhnZGRfbW9udGhfam9pbmVkKSkpDQppZiAoIWFsbChjKCJub21icmVfcmVmIiwieW0iLCJnZGRfbSIpICVpbiUgbmFtZXMoZ2RkX21vbnRoX2pvaW5lZCkpKSB7DQogIHN0b3AoImdkZF9tb250aF9qb2luZWQgZGViZSB0ZW5lciAnbm9tYnJlX3JlZicsICd5bScgeSAnZ2RkX20nLiIpDQp9DQpnZGRfbW9udGhfam9pbmVkWywgbm9tYnJlX3JlZiA6PSBhcy5jaGFyYWN0ZXIobm9tYnJlX3JlZildDQpnZGRfbW9udGhfam9pbmVkWywgeW0gOj0gYXMuRGF0ZSh5bSldDQoNCiMgU2kgZXhpc3RpZXJhIG3DoXMgZGUgdW4gbnJvIGNvbiBlbCBtaXNtbyBub21icmUgZW4gdW4gbWVzLCBzdW1vIGdkZF9tDQpnZGRfYnlfbm9tYnJlIDwtIGdkZF9tb250aF9qb2luZWRbIWlzLm5hKG5vbWJyZV9yZWYpICYgIWlzLm5hKHltKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuKGdkZF9tID0gc3VtKGdkZF9tLCBuYS5ybSA9IFRSVUUpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IC4obm9tYnJlID0gbm9tYnJlX3JlZiwgeW0pXQ0KDQojIC0tLSA1KSBGZWF0dXJlcyBpbnRlZ3JhZGFzOiBtZXJnZSBwb3Igbm9tYnJlICsgeW0gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpmZWF0X21vbnRoIDwtIG1lcmdlKA0KICBtb250aGx5X2J5X3N0YXRpb24sDQogIGdkZF9ieV9ub21icmUsDQogIGJ5ID0gYygibm9tYnJlIiwieW0iKSwNCiAgYWxsLnggPSBUUlVFDQopDQoNCiMgT3JkZW4gc3VnZXJpZG8gc2kgZXNhcyBjb2x1bW5hcyBleGlzdGVuDQpjb2xzX3RhcmdldCA8LSBjKCJub21icmUiLCJ5bSIsInkiLCJkcyIsImdkZF9tIiwibGF0aXR1ZCIsImxvbmdpdHVkIiwicHJvdmluY2lhIiwNCiAgICAgICAgICAgICAgICAgInRlbXBfbSIsImh1bV9tIiwiZmZfbSIsInBubV9tIiwicHBfbSIsIm5kYXlzIikNCnNldGNvbG9yZGVyKGZlYXRfbW9udGgsIGMoaW50ZXJzZWN0KGNvbHNfdGFyZ2V0LCBuYW1lcyhmZWF0X21vbnRoKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNldGRpZmYobmFtZXMoZmVhdF9tb250aCksIGNvbHNfdGFyZ2V0KSkpDQoNCi5zYXZlX3JkcyhmZWF0X21vbnRoLCAiZGF0YS9mZWF0X21vbnRoIikNCg0KIyAtLS0gNikgQ2hvaWNlcyBiw6FzaWNvcyBwYXJhIGxhIGFwcCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmVzdGFjaW9uZXMgPC0gc29ydCh1bmlxdWUobmEub21pdChmZWF0X21vbnRoJG5vbWJyZSkpKQ0KYW5pb3MgICAgICA8LSBzb3J0KHVuaXF1ZSh5ZWFyKGZlYXRfbW9udGgkeW0pKSkgICMgYcOxb3MgcHJlc2VudGVzIGVuIGxvcyBkYXRvcyBtZW5zdWFsZXMNCi5zYXZlX3Jkcyhlc3RhY2lvbmVzLCAiZGF0YS9lc3RhY2lvbmVzIikNCi5zYXZlX3JkcyhhbmlvcywgICAgICAiZGF0YS9hbmlvcyIpDQoNCm1lc3NhZ2UoIj09PSBMaXN0by4gTWVyZ2UgcG9yIG5vbWJyZSArIHltOyBzaW4gcmVxdWVyaXIgbnJvIGVuIG1vbnRobHlfYnlfc3RhdGlvbiA9PT0iKQ0KDQpgYGANCg0KIyMgRGVzY2FyZ2EgZGUgZ3LDoWZpY29zIGVuIG1lbW9yaWEgcGFyYSBleHBvbmVyDQoNClBvciDDumx0aW1vLCBkZWZpbmltb3MgdW5hIGZ1bmNpw7NuIGRlIGF5dWRhIChzYXZlX2FsbF9wbG90cykgcXVlIHJlY29ycmUgZWwgZW50b3JubyBkZSBSLCBlbmN1ZW50cmEgdG9kb3MgbG9zIG9iamV0b3MgZGUgZ3LDoWZpY29zIChnZ3Bsb3QsIHBsb3RseSwgbGVhZmxldCwgdHJlbGxpcykgcXVlIGNyZWFtb3MgZHVyYW50ZSBlc3RlIGFuw6FsaXNpcyB5IGxvcyBndWFyZGEgYXV0b23DoXRpY2FtZW50ZSBlbiBkaXNjbyAoY29tbyBQTkcsIFBERiwgSFRNTCkgZW4gbGEgY2FycGV0YSBmaWdzLy4NCg0KRXN0byBub3MgcGVybWl0ZSBleHBvcnRhciBmw6FjaWxtZW50ZSB0b2RhcyBsYXMgdmlzdWFsaXphY2lvbmVzIGVuIGxhIHByb3BpYSBzaGlueSBhcHAuDQoNCmBgYHtyfQ0Kc2F2ZV9hbGxfcGxvdHMgPC0gZnVuY3Rpb24oDQogIGRpcl9vdXQgPSAiZmlncyIsDQogIGZvcm1hdHMgPSBjKCJwbmciLCJwZGYiLCJzdmciKSwNCiAgd2lkdGggPSA3LCBoZWlnaHQgPSA1LCBkcGkgPSAzMDAsDQogIGVudiA9IC5HbG9iYWxFbnYNCikgew0KICBkaXIuY3JlYXRlKGRpcl9vdXQsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQ0KDQogIG9ianMgPC0gbHMoZW52aXIgPSBlbnYsIGFsbC5uYW1lcyA9IFRSVUUpDQogIG5fc2F2ZWQgPC0gMA0KDQogIGZvciAobm0gaW4gb2Jqcykgew0KICAgIG9iaiA8LSBnZXQobm0sIGVudmlyID0gZW52KQ0KDQogICAgIyAtLS0gZ2dwbG90MiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAgIGlmIChpbmhlcml0cyhvYmosICJnZ3Bsb3QiKSkgew0KICAgICAgZm9yIChmbXQgaW4gZm9ybWF0cykgew0KICAgICAgICBmIDwtIGZpbGUucGF0aChkaXJfb3V0LCBwYXN0ZTAobm0sICIuIiwgZm10KSkNCiAgICAgICAgdHJ5KHsNCiAgICAgICAgICBnZ3Bsb3QyOjpnZ3NhdmUoDQogICAgICAgICAgICBmaWxlbmFtZSA9IGYsIHBsb3QgPSBvYmosDQogICAgICAgICAgICB3aWR0aCA9IHdpZHRoLCBoZWlnaHQgPSBoZWlnaHQsIGRwaSA9IGRwaSwgdW5pdHMgPSAiaW4iLCBkZXZpY2UgPSBmbXQNCiAgICAgICAgICApDQogICAgICAgICAgbl9zYXZlZCA8LSBuX3NhdmVkICsgMQ0KICAgICAgICB9LCBzaWxlbnQgPSBUUlVFKQ0KICAgICAgfQ0KICAgICAgbmV4dA0KICAgIH0NCg0KICAgICMgLS0tIGh0bWx3aWRnZXRzOiBwbG90bHkvbGVhZmxldC9oaWdoY2hhcnRlciAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgICBpZiAoaW5oZXJpdHMob2JqLCAiaHRtbHdpZGdldCIpKSB7DQogICAgICBmIDwtIGZpbGUucGF0aChkaXJfb3V0LCBwYXN0ZTAobm0sICIuaHRtbCIpKQ0KICAgICAgdHJ5KHsNCiAgICAgICAgaHRtbHdpZGdldHM6OnNhdmVXaWRnZXQob2JqLCBmaWxlID0gZiwgc2VsZmNvbnRhaW5lZCA9IFRSVUUpDQogICAgICAgIG5fc2F2ZWQgPC0gbl9zYXZlZCArIDENCiAgICAgIH0sIHNpbGVudCA9IFRSVUUpDQogICAgICBuZXh0DQogICAgfQ0KDQogICAgIyAtLS0gbGF0dGljZS90cmVsbGlzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAgIGlmIChpbmhlcml0cyhvYmosICJ0cmVsbGlzIikpIHsNCiAgICAgIGZvciAoZm10IGluIGZvcm1hdHMpIHsNCiAgICAgICAgZiA8LSBmaWxlLnBhdGgoZGlyX291dCwgcGFzdGUwKG5tLCAiLiIsIGZtdCkpDQogICAgICAgIHRyeSh7DQogICAgICAgICAgc3dpdGNoKGZtdCwNCiAgICAgICAgICAgIHBuZyA9IHsNCiAgICAgICAgICAgICAgcG5nKGYsIHdpZHRoID0gd2lkdGgsIGhlaWdodCA9IGhlaWdodCwgdW5pdHMgPSAiaW4iLCByZXMgPSBkcGkpDQogICAgICAgICAgICAgIHByaW50KG9iaik7IGRldi5vZmYoKQ0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHBkZiA9IHsNCiAgICAgICAgICAgICAgcGRmKGYsIHdpZHRoID0gd2lkdGgsIGhlaWdodCA9IGhlaWdodCkNCiAgICAgICAgICAgICAgcHJpbnQob2JqKTsgZGV2Lm9mZigpDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgc3ZnID0gew0KICAgICAgICAgICAgICBzdmcoZiwgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0KQ0KICAgICAgICAgICAgICBwcmludChvYmopOyBkZXYub2ZmKCkNCiAgICAgICAgICAgIH0sDQogICAgICAgICAgICB7IH0gIyBvdHJvcyBmb3JtYXRvcyBzZSBwdWVkZW4gYWdyZWdhciBhY8OhDQogICAgICAgICAgKQ0KICAgICAgICAgIG5fc2F2ZWQgPC0gbl9zYXZlZCArIDENCiAgICAgICAgfSwgc2lsZW50ID0gVFJVRSkNCiAgICAgIH0NCiAgICAgIG5leHQNCiAgICB9DQoNCiAgICAjIC0tLSBncsOhZmljb3MgYmFzZSBndWFyZGFkb3MgY29uIHJlY29yZFBsb3QoKSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAgIGlmIChpbmhlcml0cyhvYmosICJyZWNvcmRlZHBsb3QiKSkgew0KICAgICAgZl9wbmcgPC0gZmlsZS5wYXRoKGRpcl9vdXQsIHBhc3RlMChubSwgIi5wbmciKSkNCiAgICAgIHRyeSh7DQogICAgICAgIHBuZyhmX3BuZywgd2lkdGggPSB3aWR0aCwgaGVpZ2h0ID0gaGVpZ2h0LCB1bml0cyA9ICJpbiIsIHJlcyA9IGRwaSkNCiAgICAgICAgcmVwbGF5UGxvdChvYmopDQogICAgICAgIGRldi5vZmYoKQ0KICAgICAgICBuX3NhdmVkIDwtIG5fc2F2ZWQgKyAxDQogICAgICB9LCBzaWxlbnQgPSBUUlVFKQ0KICAgICAgbmV4dA0KICAgIH0NCiAgfQ0KDQogIG1lc3NhZ2Uoc3ByaW50ZigiTGlzdG86IGd1YXJkYWRvcyAlZCBhcmNoaXZvcyBlbiAnJXMnLiIsIG5fc2F2ZWQsIGRpcl9vdXQpKQ0KfQ0KDQojIEVqZWN1dMOhOg0Kc2F2ZV9hbGxfcGxvdHMoZGlyX291dCA9ICJmaWdzIiwgZm9ybWF0cyA9IGMoInBuZyIsInBkZiIsInN2ZyIpLCB3aWR0aCA9IDcsIGhlaWdodCA9IDUsIGRwaSA9IDMwMCkNCg0KYGBgDQo=